diff --git a/.circleci/config.yml b/.circleci/config.yml index c11f87b6f59..7f9324956ca 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,15 +39,18 @@ aliases: command: gulp e2e-test # Download and run BrowserStack local - - &setup_browserstack - name : Download BrowserStack Local binary and start it. + - &download_browserstack + name : Download BrowserStackLocal binary command : | # Download the browserstack binary file wget "https://www.browserstack.com/browserstack-local/BrowserStackLocal-linux-x64.zip" # Unzip it unzip BrowserStackLocal-linux-x64.zip - # Run the file with user's access key - ./BrowserStackLocal ${BROWSERSTACK_ACCESS_KEY} & + + - &start_browserstack + name: Start BrowserStackLocal + command: ./BrowserStackLocal --key ${BROWSERSTACK_ACCESS_KEY} --automate-only --local-identifier ${CIRCLE_WORKFLOW_JOB_ID} + background: true - &unit_test_steps - checkout @@ -55,7 +58,8 @@ aliases: - run: npm ci - save_cache: *save_dep_cache - run: *install - - run: *setup_browserstack + - run: *download_browserstack + - run: *start_browserstack - run: *run_unit_test - &endtoend_test_steps @@ -64,7 +68,8 @@ aliases: - run: npm install - save_cache: *save_dep_cache - run: *install - - run: *setup_browserstack + - run: *download_browserstack + - run: *start_browserstack - run: *run_endtoend_test version: 2 diff --git a/.eslintrc.js b/.eslintrc.js index 95515e8ba98..06a5e81d9f5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,7 +21,8 @@ module.exports = { globals: { '$$PREBID_GLOBAL$$': false, 'BROWSERSTACK_USERNAME': false, - 'BROWSERSTACK_KEY': false + 'BROWSERSTACK_KEY': false, + 'FEATURES': 'readonly', }, // use babel as parser for fancy syntax parser: '@babel/eslint-parser', diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9fdb04ba556..09ef5c445f2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,15 @@ ## Type of change @@ -11,14 +21,16 @@ Thank you for your pull request. Please make sure this PR is scoped to one chang - [ ] Refactoring (no functional changes, no api changes) - [ ] Build related changes - [ ] CI related changes + - [ ] Does this change affect user-facing APIs or examples documented on http://prebid.org? - [ ] Other ## Description of change - -- test parameters for validating bids + -- A link to a PR on the docs repo at https://github.com/prebid/prebid.github.io/ ## Other information diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 00000000000..2e8465003e4 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,4 @@ +paths: + - src + - modules + - libraries diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..5ace4600a1f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000000..9b7c39d53d0 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,73 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '22 11 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + config-file: ./.github/codeql/codeql-config.yml + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/issue_tracker.yml b/.github/workflows/issue_tracker.yml index 4397337b4c7..05d08b2b0d7 100644 --- a/.github/workflows/issue_tracker.yml +++ b/.github/workflows/issue_tracker.yml @@ -3,13 +3,18 @@ on: issues: types: - opened +permissions: + contents: read + jobs: track_issue: + permissions: + contents: none runs-on: ubuntu-latest steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0 + uses: tibdex/github-app-token@f717b5ecd4534d3c4df4ce9b5c1c2214f0f7cd06 with: app_id: ${{ secrets.ISSUE_APP_ID }} private_key: ${{ secrets.ISSUE_APP_PEM }} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 8152b61275d..a13237f1290 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -6,8 +6,14 @@ on: branches: - master +permissions: + contents: read + jobs: update_release_draft: + permissions: + contents: write # for release-drafter/release-drafter to create a github release + pull-requests: write # for release-drafter/release-drafter to add label to PR runs-on: ubuntu-latest steps: # Drafts your next Release notes as Pull Requests are merged into "master" diff --git a/.gitignore b/.gitignore index c0452b7b3d0..e5f000dd4d5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ selenium*.log integrationExamples/gpt/gpt.html integrationExamples/gpt/*-test.html integrationExamples/implementations/ -src/adapters/analytics/libraries +libraries/analyticsAdapter/examples/libraries # Coverage reports build/coverage/ diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 2934a30fb47..45ca30a7a3d 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -128,6 +128,10 @@ Follow steps above for general review process. In addition: - Consider whether the kind of data the module is obtaining could have privacy implications. If so, make sure they're utilizing the `consent` data passed to them. - Make sure there's a docs pull request +### Reviewing changes to the `debugging` module + +The debugging module cannot import from core in the same way that other modules can. See this [warning](https://github.com/prebid/Prebid.js/blob/master/modules/debugging/WARNING.md) for more details. + ## Ticket Coordinator Each week, Prebid Org assigns one person to keep an eye on incoming issues and PRs. Every Monday morning a reminder is sent to the prebid-js slack channel with a link to the spreadsheet. If you're on rotation, please check that list each Monday to see if you're on-duty. diff --git a/README.md b/README.md index 42d747b20b6..bbc0d79ab41 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ [![Build Status](https://circleci.com/gh/prebid/Prebid.js.svg?style=svg)](https://circleci.com/gh/prebid/Prebid.js) [![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") -[![Code Climate](https://codeclimate.com/github/prebid/Prebid.js/badges/gpa.svg)](https://codeclimate.com/github/prebid/Prebid.js) [![Coverage Status](https://coveralls.io/repos/github/prebid/Prebid.js/badge.svg)](https://coveralls.io/github/prebid/Prebid.js) -[![devDependencies Status](https://david-dm.org/prebid/Prebid.js/dev-status.svg)](https://david-dm.org/prebid/Prebid.js?type=dev) [![Total Alerts](https://img.shields.io/lgtm/alerts/g/prebid/Prebid.js.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/prebid/Prebid.js/alerts/) # Prebid.js @@ -91,7 +89,7 @@ Or for Babel 6: } ``` -Then you can use Prebid.js as any other npm depedendency +Then you can use Prebid.js as any other npm dependency ```javascript import pbjs from 'prebid.js'; @@ -193,8 +191,43 @@ Most likely your custom `prebid.js` will only change when there's: Having said that, you are probably safe to check your custom bundle into your project. You can also generate it in your build process. +**Build once, bundle multiple times** + +If you need to generate multiple distinct bundles from the same Prebid version, you can reuse a single build with: + +``` +gulp build +gulp bundle --tag one --modules=one.json +gulp bundle --tag two --modules=two.json +``` + +This generates slightly larger files, but has the advantage of being much faster to run (after the initial `gulp build`). It's also the method used by [the Prebid.org download page](https://docs.prebid.org/download.html). + +### Excluding particular features from the build + +Since version 7.2.0, you may instruct the build to exclude code for some features - for example, if you don't need support for native ads: + +``` +gulp build --disable NATIVE --modules=openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter # substitute your module list +``` + +Or, if you are consuming Prebid through npm, with the `disableFeatures` option in your Prebid rule: + +```javascript + { + test: /.js$/, + include: new RegExp(`\\${path.sep}prebid\\.js`), + use: { + loader: 'babel-loader', + options: require('prebid.js/babelConfig.js')({disableFeatures: ['NATIVE']}) + } + } +``` + +**Note**: this is still a work in progress - at the moment, `NATIVE` is the only feature that can be disabled this way, resulting in a minimal decrease in size (but you can expect that to improve over time). + ## Test locally To lint the code: diff --git a/RELEASE_SCHEDULE.md b/RELEASE_SCHEDULE.md index b68495ed4ae..45f4e6c7dc5 100644 --- a/RELEASE_SCHEDULE.md +++ b/RELEASE_SCHEDULE.md @@ -12,7 +12,7 @@ We aim to push a new release of Prebid.js every week on Tuesday. While the releases will be available immediately for those using direct Git access, -it will be about a week before the Prebid Org [Download Page](http://prebid.org/download.html) will be updated. +it will be about a week before the Prebid Org [Download Page](https://docs.prebid.org/download.html) will be updated. You can determine what is in a given build using the [releases page](https://github.com/prebid/Prebid.js/releases) diff --git a/allowedModules.js b/allowedModules.js index be9a2dc2abf..3e6e3039fa2 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -15,5 +15,8 @@ module.exports = { 'just-clone', 'dlv', 'dset' + ], + 'libraries': [ + ...sharedWhiteList // empty for now, but keep it to enable linting ] }; diff --git a/babelConfig.js b/babelConfig.js index c1ddc11b689..a88491c0cae 100644 --- a/babelConfig.js +++ b/babelConfig.js @@ -9,7 +9,7 @@ function useLocal(module) { }) } -module.exports = function (test = false) { +module.exports = function (options = {}) { return { 'presets': [ [ @@ -18,13 +18,19 @@ module.exports = function (test = false) { 'useBuiltIns': 'entry', 'corejs': '3.13.0', // a lot of tests use sinon.stub & others that stopped working on ES6 modules with webpack 5 - 'modules': test ? 'commonjs' : 'auto', + 'modules': options.test ? 'commonjs' : 'auto', } ] ], - 'plugins': [ - path.resolve(__dirname, './plugins/pbjsGlobals.js'), - useLocal('babel-plugin-transform-object-assign'), - ], + 'plugins': (() => { + const plugins = [ + [path.resolve(__dirname, './plugins/pbjsGlobals.js'), options], + [useLocal('@babel/plugin-transform-runtime')], + ]; + if (options.codeCoverage) { + plugins.push([useLocal('babel-plugin-istanbul')]) + } + return plugins; + })(), } } diff --git a/bundle-template.txt b/bundle-template.txt new file mode 100644 index 00000000000..2f58aedfe81 --- /dev/null +++ b/bundle-template.txt @@ -0,0 +1,16 @@ +/* <%= prebid.name %> v<%= prebid.version %> +Updated: <%= (new Date()).toISOString().substring(0, 10) %> +Modules: <%= modules %> */ + +if (!window.<%= prebid.globalVarName %> || !window.<%= prebid.globalVarName %>.libLoaded) { + $$PREBID_SOURCE$$ + <% if(enable) {%> + <%= prebid.globalVarName %>.processQueue(); + <% } %> +} else { + try { + if(window.<%= prebid.globalVarName %>.getConfig('debug')) { + console.warn('Attempted to load a copy of Prebid.js that clashes with the existing \'<%= prebid.globalVarName %>\' instance. Load aborted.'); + } + } catch (e) {} +} diff --git a/features.json b/features.json new file mode 100644 index 00000000000..c0f7e4d75a9 --- /dev/null +++ b/features.json @@ -0,0 +1,3 @@ +[ + "NATIVE" +] diff --git a/governance.md b/governance.md index 3d00f067194..b1446a22373 100644 --- a/governance.md +++ b/governance.md @@ -9,15 +9,9 @@ This document describes the governance model for the Prebid project. The Prebid ### Roles and Responsibilities: - **User:** Any individual who consumes / uses the Prebid.js library. -- **Contributor:** Any individual who contributes code that is subsequently merged to the project. Contributed code is governed by the Prebid.js [license](https://github.com/prebid/Prebid.js/blob/master/LICENSE). Contributors are required to sign a CLA before any code can be committed (CLA pending). -- **Core Team Member:** An individual contributor who has been appointed by the Tech Lead on the project to maintain it and further it’s stated goals. -- **Tech Lead:** The Tech Lead is responsible for overall technical direction of the project. The Tech Lead will work closely with Core Team members to facilitate development and further the project goals. +- **Contributor:** Any individual who contributes code that is subsequently merged to the project. Contributed code is governed by the Prebid.js [license](https://github.com/prebid/Prebid.js/blob/master/LICENSE). +- **Core & Review Team Member:** An individual contributor who has been appointed by the Tech Lead on the project to maintain it and further it’s stated goals. +- **Identity Team Member:** An individual contributor who has been appointed by the Identity PMC to review and maintain the identity modules and further the PMC stated goals. +- **Tech Lead:** The Tech Lead is responsible for overall technical direction of the project & serves as the PMC chair. The Tech Lead will work closely with Core Team members to facilitate development and further the project goals. -### Current Prebid.js Core Team -- @mkendall07 (Tech Lead) -- @jsnellbaker -- @matthewlane -- @jaiminpanchal27 -- @snapwich -- @harpere -- @mike-chowla +The Core team is currently visible at https://github.com/orgs/prebid/teams/core/members to project members. diff --git a/gulpHelpers.js b/gulpHelpers.js index c0946edf93d..1eec08b7a3e 100644 --- a/gulpHelpers.js +++ b/gulpHelpers.js @@ -6,7 +6,7 @@ const MANIFEST = 'package.json'; const through = require('through2'); const _ = require('lodash'); const gutil = require('gulp-util'); -const submodules = require('./modules/.submodules.json'); +const submodules = require('./modules/.submodules.json').parentModules; const MODULE_PATH = './modules'; const BUILD_PATH = './build/dist'; @@ -105,16 +105,20 @@ module.exports = { }, internalModules)); }), + getBuiltPath(dev, assetPath) { + return path.join(__dirname, dev ? DEV_PATH : BUILD_PATH, assetPath) + }, + getBuiltModules: function(dev, externalModules) { var modules = this.getModuleNames(externalModules); if (Array.isArray(externalModules)) { modules = _.intersection(modules, externalModules); } - return modules.map(name => path.join(__dirname, dev ? DEV_PATH : BUILD_PATH, name + '.js')); + return modules.map(name => this.getBuiltPath(dev, name + '.js')); }, getBuiltPrebidCoreFile: function(dev) { - return path.join(__dirname, dev ? DEV_PATH : BUILD_PATH, 'prebid-core' + '.js'); + return this.getBuiltPath(dev, 'prebid-core.js') }, getModulePaths: function(externalModules) { @@ -169,5 +173,11 @@ module.exports = { } return options; - } + }, + getDisabledFeatures() { + return (argv.disable || '') + .split(',') + .map((s) => s.trim()) + .filter((s) => s); + }, }; diff --git a/gulpfile.js b/gulpfile.js index ff49436384b..09de874e389 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,14 +9,10 @@ var connect = require('gulp-connect'); var webpack = require('webpack'); var webpackStream = require('webpack-stream'); var gulpClean = require('gulp-clean'); -var KarmaServer = require('karma').Server; -var karmaConfMaker = require('./karma.conf.maker.js'); var opens = require('opn'); var webpackConfig = require('./webpack.conf.js'); var helpers = require('./gulpHelpers.js'); var concat = require('gulp-concat'); -var header = require('gulp-header'); -var footer = require('gulp-footer'); var replace = require('gulp-replace'); var shell = require('gulp-shell'); var eslint = require('gulp-eslint'); @@ -27,14 +23,15 @@ var fs = require('fs'); var jsEscape = require('gulp-js-escape'); const path = require('path'); const execa = require('execa'); +const {minify} = require('terser'); +const Vinyl = require('vinyl'); var prebid = require('./package.json'); -var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); -var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + '*/\n'; var port = 9999; const INTEG_SERVER_HOST = argv.host ? argv.host : 'localhost'; const INTEG_SERVER_PORT = 4444; -const { spawn } = require('child_process'); +const { spawn, fork } = require('child_process'); +const TerserPlugin = require('terser-webpack-plugin'); // these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules var explicitModules = [ @@ -73,6 +70,7 @@ function lint(done) { return gulp.src([ 'src/**/*.js', 'modules/**/*.js', + 'libraries/**/*.js', 'test/**/*.js', 'plugins/**/*.js', '!plugins/**/node_modules/**', @@ -120,6 +118,15 @@ function makeDevpackPkg() { devtool: 'source-map', mode: 'development' }) + + const babelConfig = require('./babelConfig.js')({disableFeatures: helpers.getDisabledFeatures(), prebidDistUrlBase: argv.distUrlBase || '/build/dev/'}); + + // update babel config to set local dist url + cloned.module.rules + .flatMap((rule) => rule.use) + .filter((use) => use.loader === 'babel-loader') + .forEach((use) => use.options = Object.assign({}, use.options, babelConfig)); + var externalModules = helpers.getArgModules(); const analyticsSources = helpers.getAnalyticsSources(); @@ -132,35 +139,31 @@ function makeDevpackPkg() { .pipe(connect.reload()); } -function makeWebpackPkg() { - var cloned = _.cloneDeep(webpackConfig); +function makeWebpackPkg(extraConfig = {}) { + var cloned = _.merge(_.cloneDeep(webpackConfig), extraConfig); if (!argv.sourceMaps) { delete cloned.devtool; } - var externalModules = helpers.getArgModules(); - - const analyticsSources = helpers.getAnalyticsSources(); - const moduleSources = helpers.getModulePaths(externalModules); - - return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) - .pipe(helpers.nameModules(externalModules)) - .pipe(webpackStream(cloned, webpack)) - .pipe(gulp.dest('build/dist')); -} + return function buildBundle() { + var externalModules = helpers.getArgModules(); -function addBanner() { - const sm = argv.sourceMaps; + const analyticsSources = helpers.getAnalyticsSources(); + const moduleSources = helpers.getModulePaths(externalModules); - return gulp.src(['build/dist/prebid-core.js']) - .pipe(gulpif(sm, sourcemaps.init({loadMaps: true}))) - .pipe(header(banner, {prebid})) - .pipe(gulpif(sm, sourcemaps.write('.'))) - .pipe(gulp.dest('build/dist')) + return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) + .pipe(helpers.nameModules(externalModules)) + .pipe(webpackStream(cloned, webpack)) + .pipe(gulp.dest('build/dist')); + } } function getModulesListToAddInBanner(modules) { - return (modules.length > 0) ? modules.join(', ') : 'All available modules in current version.'; + if (!modules || modules.length === helpers.getModuleNames().length) { + return 'All available modules for this version.' + } else { + return modules.join(', ') + } } function gulpBundle(dev) { @@ -180,6 +183,47 @@ function nodeBundle(modules, dev = false) { }); } +function wrapWithHeaderAndFooter(dev, modules) { + // NOTE: gulp-header, gulp-footer & gulp-wrap do not play nice with source maps. + // gulp-concat does; for that reason we are prepending and appending the source stream with "fake" header & footer files. + function memoryVinyl(name, contents) { + return new Vinyl({ + cwd: '', + base: 'generated', + path: name, + contents: Buffer.from(contents, 'utf-8') + }); + } + return function wrap(stream) { + const wrapped = through.obj(); + const placeholder = '$$PREBID_SOURCE$$'; + const tpl = _.template(fs.readFileSync('./bundle-template.txt'))({ + prebid, + modules: getModulesListToAddInBanner(modules), + enable: !argv.manualEnable + }); + (dev ? Promise.resolve(tpl) : minify(tpl, {format: {comments: true}}).then((res) => res.code)) + .then((tpl) => { + // wrap source placeholder in an IIFE to make it an expression (so that it works with minify output) + const parts = tpl.replace(placeholder, `(function(){$$${placeholder}$$})()`).split(placeholder); + if (parts.length !== 2) { + throw new Error(`Cannot parse bundle template; it must contain exactly one instance of '${placeholder}'`); + } + const [header, footer] = parts; + wrapped.push(memoryVinyl('prebid-header.js', header)); + stream.pipe(wrapped, {end: false}); + stream.on('end', () => { + wrapped.push(memoryVinyl('prebid-footer.js', footer)); + wrapped.push(null); + }); + }) + .catch((err) => { + wrapped.destroy(err); + }); + return wrapped; + } +} + function bundle(dev, moduleArr) { var modules = moduleArr || helpers.getArgModules(); var allModules = helpers.getModuleNames(modules); @@ -196,8 +240,15 @@ function bundle(dev, moduleArr) { }); } } + const coreFile = helpers.getBuiltPrebidCoreFile(dev); + const moduleFiles = helpers.getBuiltModules(dev, modules); + const depGraph = require(helpers.getBuiltPath(dev, 'dependencies.json')); + const dependencies = new Set(); + [coreFile].concat(moduleFiles).map(name => path.basename(name)).forEach((file) => { + (depGraph[file] || []).forEach((dep) => dependencies.add(helpers.getBuiltPath(dev, dep))); + }); - var entries = [helpers.getBuiltPrebidCoreFile(dev)].concat(helpers.getBuiltModules(dev, modules)); + const entries = [coreFile].concat(Array.from(dependencies), moduleFiles); var outputFileName = argv.bundleName ? argv.bundleName : 'prebid.js'; @@ -210,17 +261,10 @@ function bundle(dev, moduleArr) { gutil.log('Appending ' + prebid.globalVarName + '.processQueue();'); gutil.log('Generating bundle:', outputFileName); - return gulp.src( - entries - ) - // Need to uodate the "Modules: ..." section in comment with the current modules list - .pipe(replace(/(Modules: )(.*?)(\*\/)/, ('$1' + getModulesListToAddInBanner(helpers.getArgModules()) + ' $3'))) + const wrap = wrapWithHeaderAndFooter(dev, modules); + return wrap(gulp.src(entries)) .pipe(gulpif(sm, sourcemaps.init({ loadMaps: true }))) .pipe(concat(outputFileName)) - .pipe(gulpif(!argv.manualEnable, footer('\n<%= global %>.processQueue();', { - global: prebid.globalVarName - } - ))) .pipe(gulpif(sm, sourcemaps.write('.'))); } @@ -236,9 +280,11 @@ function bundle(dev, moduleArr) { function testTaskMaker(options = {}) { ['watch', 'e2e', 'file', 'browserstack', 'notest'].forEach(opt => { - options[opt] = options[opt] || argv[opt]; + options[opt] = options.hasOwnProperty(opt) ? options[opt] : argv[opt]; }) + options.disableFeatures = options.disableFeatures || helpers.getDisabledFeatures(); + return function test(done) { if (options.notest) { done(); @@ -259,14 +305,7 @@ function testTaskMaker(options = {}) { process.exit(1); }); } else { - var karmaConf = karmaConfMaker(false, options.browserstack, options.watch, options.file); - - var browserOverride = helpers.parseBrowserArgs(argv); - if (browserOverride.length > 0) { - karmaConf.browsers = browserOverride; - } - - new KarmaServer(karmaConf, newKarmaCallback(done)).start(); + runKarma(options, done) } } } @@ -293,25 +332,24 @@ function runWebdriver({file}) { return execa(wdioCmd, wdioOpts, { stdio: 'inherit' }); } -function newKarmaCallback(done) { - return function (exitCode) { +function runKarma(options, done) { + // the karma server appears to leak memory; starting it multiple times in a row will run out of heap + // here we run it in a separate process to bypass the problem + options = Object.assign({browsers: helpers.parseBrowserArgs(argv)}, options) + const child = fork('./karmaRunner.js'); + child.on('exit', (exitCode) => { if (exitCode) { done(new Error('Karma tests failed with exit code ' + exitCode)); - if (argv.browserstack) { - process.exit(exitCode); - } } else { done(); - if (argv.browserstack) { - process.exit(exitCode); - } } - } + }) + child.send(options); } // If --file "" is given, the task will only run tests in the specified file. function testCoverage(done) { - new KarmaServer(karmaConfMaker(true, false, false, argv.file), newKarmaCallback(done)).start(); + runKarma({coverage: true, browserstack: false, watch: false, file: argv.file}, done); } function coveralls() { // 2nd arg is a dependency: 'test' must be finished @@ -389,11 +427,30 @@ gulp.task(clean); gulp.task(escapePostbidConfig); gulp.task('build-bundle-dev', gulp.series(makeDevpackPkg, gulpBundle.bind(null, true))); -gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg, addBanner, gulpBundle.bind(null, false))); +gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg(), gulpBundle.bind(null, false))); +// build-bundle-verbose - prod bundle except names and comments are preserved. Use this to see the effects +// of dead code elimination. +gulp.task('build-bundle-verbose', gulp.series(makeWebpackPkg({ + optimization: { + minimizer: [ + new TerserPlugin({ + parallel: true, + terserOptions: { + mangle: false, + format: { + comments: 'all' + } + }, + extractComments: false, + }), + ], + } +}), gulpBundle.bind(null, false))); // public tasks (dependencies are needed for each task since they can be ran on their own) gulp.task('test-only', test); -gulp.task('test', gulp.series(clean, lint, 'test-only')); +gulp.task('test-all-features-disabled', testTaskMaker({disableFeatures: require('./features.json'), oneBrowser: 'chrome', watch: false})); +gulp.task('test', gulp.series(clean, lint, gulp.series('test-all-features-disabled', 'test-only'))); gulp.task('test-coverage', gulp.series(clean, testCoverage)); gulp.task(viewCoverage); @@ -405,13 +462,14 @@ gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test))); gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast))); +gulp.task('serve-prod', gulp.series(clean, gulp.parallel('build-bundle-prod', startLocalServer))); gulp.task('serve-and-test', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast, testTaskMaker({watch: true})))); -gulp.task('serve-e2e', gulp.series(clean, 'build-bundle-prod', gulp.parallel(() => startIntegServer(), startLocalServer))) -gulp.task('serve-e2e-dev', gulp.series(clean, 'build-bundle-dev', gulp.parallel(() => startIntegServer(true), startLocalServer))) +gulp.task('serve-e2e', gulp.series(clean, 'build-bundle-prod', gulp.parallel(() => startIntegServer(), startLocalServer))); +gulp.task('serve-e2e-dev', gulp.series(clean, 'build-bundle-dev', gulp.parallel(() => startIntegServer(true), startLocalServer))); gulp.task('default', gulp.series(clean, 'build-bundle-prod')); -gulp.task('e2e-test-only', () => runWebdriver({file: argv.file})) +gulp.task('e2e-test-only', () => runWebdriver({file: argv.file})); gulp.task('e2e-test', gulp.series(clean, 'build-bundle-prod', testTaskMaker({e2e: true}))); // other tasks gulp.task(bundleToStdout); diff --git a/integrationExamples/gpt/1plusXRtdProviderExample.html b/integrationExamples/gpt/1plusXRtdProviderExample.html new file mode 100644 index 00000000000..b2c7a0037ff --- /dev/null +++ b/integrationExamples/gpt/1plusXRtdProviderExample.html @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + +

1plusX RTD Module for Prebid

+ +
+ +
+ + + diff --git a/integrationExamples/gpt/airgridRtdProvider_example.html b/integrationExamples/gpt/airgridRtdProvider_example.html index a8fd989f682..657eae8f481 100644 --- a/integrationExamples/gpt/airgridRtdProvider_example.html +++ b/integrationExamples/gpt/airgridRtdProvider_example.html @@ -108,10 +108,7 @@ var gads = document.createElement("script"); gads.async = true; gads.type = "text/javascript"; - var useSSL = "https:" == document.location.protocol; - gads.src = - (useSSL ? "https:" : "http:") + - "//www.googletagservices.com/tag/js/gpt.js"; + gads.src = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js'; var node = document.getElementsByTagName("script")[0]; node.parentNode.insertBefore(gads, node); })(); diff --git a/integrationExamples/gpt/akamaidap_email_example.html b/integrationExamples/gpt/akamaidap_email_example.html deleted file mode 100755 index 828b2add787..00000000000 --- a/integrationExamples/gpt/akamaidap_email_example.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - - - - - - - -

Prebid.js Test

-
Div-1
-
- -
-
User IDs Sent to Bidding Adapter
-
- - diff --git a/integrationExamples/gpt/akamaidap_signature_example.html b/integrationExamples/gpt/akamaidap_signature_example.html deleted file mode 100644 index e4c7c617653..00000000000 --- a/integrationExamples/gpt/akamaidap_signature_example.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - -

Prebid.js Test

-
Div-1
-
- -
-
User IDs Sent to Bidding Adapter
-
- - diff --git a/integrationExamples/gpt/akamaidap_x1_example.html b/integrationExamples/gpt/akamaidap_x1_example.html deleted file mode 100755 index b1f16acc560..00000000000 --- a/integrationExamples/gpt/akamaidap_x1_example.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - -

Prebid.js Test

-
Div-1
-
- -
-
User IDs Sent to Bidding Adapter
-
- - diff --git a/integrationExamples/gpt/amp/remote.html b/integrationExamples/gpt/amp/remote.html index 785d854766f..4ee2cdcb2f6 100644 --- a/integrationExamples/gpt/amp/remote.html +++ b/integrationExamples/gpt/amp/remote.html @@ -33,7 +33,7 @@ // load Prebid.js (function () { - var d = document, pbs = d.createElement("script"), pro = d.location.protocal; + var d = document, pbs = d.createElement("script"); pbs.type = "text/javascript"; pbs.src = prebidSrc; var target = document.getElementsByTagName("head")[0]; diff --git a/integrationExamples/gpt/blueconicRtdProvider_example.html b/integrationExamples/gpt/blueconicRtdProvider_example.html new file mode 100644 index 00000000000..598a7569d8e --- /dev/null +++ b/integrationExamples/gpt/blueconicRtdProvider_example.html @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + +

BlueConic RTD Prebid

+ +
+ +
+ + + +BlueConic Real-Time Data: +
+
+ + diff --git a/integrationExamples/gpt/gdpr_hello_world.html b/integrationExamples/gpt/gdpr_hello_world.html index 2d70af8d34f..c62569cfc4f 100644 --- a/integrationExamples/gpt/gdpr_hello_world.html +++ b/integrationExamples/gpt/gdpr_hello_world.html @@ -1,83 +1,19 @@ - - + window._iub = window._iub || {}; + _iub.csConfiguration = { + cookiePolicyId: 417383, + siteId: 1, + logLevel: 'error', + lang: 'en', + enableTcf: true, + }; + + + + + - - - - - - - - - - -

Prebid.js Test

-
Div-1
-
- -
- - diff --git a/integrationExamples/gpt/hadronRtdProvider_example.html b/integrationExamples/gpt/hadronRtdProvider_example.html index 065c8379956..f90ce21921c 100644 --- a/integrationExamples/gpt/hadronRtdProvider_example.html +++ b/integrationExamples/gpt/hadronRtdProvider_example.html @@ -88,9 +88,7 @@ var gads = document.createElement('script'); gads.async = true; gads.type = 'text/javascript'; - var useSSL = 'https:' == document.location.protocol; - gads.src = (useSSL ? 'https:' : 'http:') + - '//www.googletagservices.com/tag/js/gpt.js'; + gads.src = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js'; var node = document.getElementsByTagName('script')[0]; node.parentNode.insertBefore(gads, node); })(); diff --git a/integrationExamples/gpt/inskin_example.html b/integrationExamples/gpt/inskin_example.html index 197a5b1ffe1..45fd448cf48 100644 --- a/integrationExamples/gpt/inskin_example.html +++ b/integrationExamples/gpt/inskin_example.html @@ -72,9 +72,7 @@ var gads = document.createElement('script'); gads.async = true; gads.type = 'text/javascript'; - var useSSL = 'https:' == document.location.protocol; - gads.src = (useSSL ? 'https:' : 'http:') + - '//www.googletagservices.com/tag/js/gpt.js'; + gads.src = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js'; var node = document.getElementsByTagName('script')[0]; node.parentNode.insertBefore(gads, node); })(); diff --git a/integrationExamples/gpt/permutiveRtdProvider_example.html b/integrationExamples/gpt/permutiveRtdProvider_example.html index dbb4d2af0d6..118cc678726 100644 --- a/integrationExamples/gpt/permutiveRtdProvider_example.html +++ b/integrationExamples/gpt/permutiveRtdProvider_example.html @@ -23,7 +23,7 @@ } } - setLocalStorageData() + setLocalStorageData(); var div_1_sizes = [ [300, 250], diff --git a/integrationExamples/gpt/prebidServer_example.html b/integrationExamples/gpt/prebidServer_example.html index 37902edd979..f23554369bc 100644 --- a/integrationExamples/gpt/prebidServer_example.html +++ b/integrationExamples/gpt/prebidServer_example.html @@ -12,9 +12,7 @@ var gads = document.createElement('script'); gads.async = true; gads.type = 'text/javascript'; - var useSSL = 'https:' == document.location.protocol; - gads.src = (useSSL ? 'https:' : 'http:') + - '//www.googletagservices.com/tag/js/gpt.js'; + gads.src = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js'; var node = document.getElementsByTagName('script')[0]; node.parentNode.insertBefore(gads, node); })(); diff --git a/integrationExamples/gpt/prebidServer_native_example.html b/integrationExamples/gpt/prebidServer_native_example.html index 16c7d38a427..c590f0bcee5 100644 --- a/integrationExamples/gpt/prebidServer_native_example.html +++ b/integrationExamples/gpt/prebidServer_native_example.html @@ -133,8 +133,8 @@ - \ No newline at end of file + diff --git a/karma.conf.maker.js b/karma.conf.maker.js index b5c6b44e4fd..fd8f2e6fdd8 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -7,7 +7,7 @@ var _ = require('lodash'); var webpackConf = require('./webpack.conf.js'); var karmaConstants = require('karma').constants; -function newWebpackConfig(codeCoverage) { +function newWebpackConfig(codeCoverage, disableFeatures) { // Make a clone here because we plan on mutating this object, and don't want parallel tasks to trample each other. var webpackConfig = _.cloneDeep(webpackConf); @@ -22,20 +22,9 @@ function newWebpackConfig(codeCoverage) { .flatMap((r) => r.use) .filter((use) => use.loader === 'babel-loader') .forEach((use) => { - use.options = babelConfig(true); + use.options = babelConfig({test: true, codeCoverage, disableFeatures}); }); - if (codeCoverage) { - webpackConfig.module.rules.push({ - enforce: 'post', - exclude: /(node_modules)|(test)|(integrationExamples)|(build)|polyfill.js|(src\/adapters\/analytics\/ga.js)/, - use: { - loader: '@jsdevtools/coverage-istanbul-loader', - options: { esModules: true } - }, - test: /\.js$/ - }) - } return webpackConfig; } @@ -92,7 +81,9 @@ function setBrowsers(karmaConf, browserstack) { karmaConf.browserStack = { username: process.env.BROWSERSTACK_USERNAME, accessKey: process.env.BROWSERSTACK_ACCESS_KEY, - build: 'Prebidjs Unit Tests ' + new Date().toLocaleString() + build: 'Prebidjs Unit Tests ' + new Date().toLocaleString(), + startTunnel: false, + localIdentifier: process.env.CIRCLE_WORKFLOW_JOB_ID } if (process.env.TRAVIS) { karmaConf.browserStack.startTunnel = false; @@ -117,8 +108,8 @@ function setBrowsers(karmaConf, browserstack) { } } -module.exports = function(codeCoverage, browserstack, watchMode, file) { - var webpackConfig = newWebpackConfig(codeCoverage); +module.exports = function(codeCoverage, browserstack, watchMode, file, disableFeatures) { + var webpackConfig = newWebpackConfig(codeCoverage, disableFeatures); var plugins = newPluginsArray(browserstack); var files = file ? ['test/test_deps.js', file] : ['test/test_index.js']; @@ -164,6 +155,12 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { reporters: ['mocha'], + client: { + mocha: { + timeout: 3000 + } + }, + mochaReporter: { showDiff: true, output: 'minimal' @@ -179,7 +176,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { concurrency: 6, plugins: plugins - } + }; // To ensure that, we are able to run single spec file // here we are adding preprocessors, when file is passed diff --git a/karmaRunner.js b/karmaRunner.js new file mode 100644 index 00000000000..96259069966 --- /dev/null +++ b/karmaRunner.js @@ -0,0 +1,23 @@ +const karma = require('karma'); +const process = require('process'); +const karmaConfMaker = require('./karma.conf.maker.js'); + +process.on('message', function(options) { + try { + let cfg = karmaConfMaker(options.coverage, options.browserstack, options.watch, options.file, options.disableFeatures); + if (options.browsers && options.browsers.length) { + cfg.browsers = options.browsers; + } + if (options.oneBrowser) { + cfg.browsers = [cfg.browsers.find((b) => b.toLowerCase().includes(options.oneBrowser.toLowerCase())) || cfg.browsers[0]] + } + cfg = karma.config.parseConfig(null, cfg); + new karma.Server(cfg, (exitCode) => { + process.exit(exitCode); + }).start(); + } catch (e) { + // eslint-disable-next-line + console.error(e); + process.exit(1); + } +}); diff --git a/libraries/LIBRARIES.md b/libraries/LIBRARIES.md new file mode 100644 index 00000000000..e4d8fcc4f98 --- /dev/null +++ b/libraries/LIBRARIES.md @@ -0,0 +1,5 @@ +## Cross-module libraries + +Each directory under this one is packaged into a "library" during the build. + +Modules may share code by simply importing from a common library file; if the module is included in the build, any libraries they import from will also be included. diff --git a/src/AnalyticsAdapter.js b/libraries/analyticsAdapter/AnalyticsAdapter.js similarity index 90% rename from src/AnalyticsAdapter.js rename to libraries/analyticsAdapter/AnalyticsAdapter.js index ae891966ee1..277a1455a75 100644 --- a/src/AnalyticsAdapter.js +++ b/libraries/analyticsAdapter/AnalyticsAdapter.js @@ -1,7 +1,7 @@ -import CONSTANTS from './constants.json'; -import { ajax } from './ajax.js'; -import { logMessage, _each } from './utils.js'; -import * as events from './events.js' +import CONSTANTS from '../../src/constants.json'; +import { ajax } from '../../src/ajax.js'; +import { logMessage, _each } from '../../src/utils.js'; +import * as events from '../../src/events.js' export const _internal = { ajax @@ -31,19 +31,19 @@ const { const ENDPOINT = 'endpoint'; const BUNDLE = 'bundle'; -var _sampled = true; - export default function AnalyticsAdapter({ url, analyticsType, global, handler }) { - var _queue = []; - var _eventCount = 0; - var _enableCheck = true; - var _handlers; + const _queue = []; + let _eventCount = 0; + let _enableCheck = true; + let _handlers; + let _enabled = false; + let _sampled = true; if (analyticsType === ENDPOINT || BUNDLE) { _emptyQueue(); } - return { + return Object.defineProperties({ track: _track, enqueue: _enqueue, enableAnalytics: _enable, @@ -52,7 +52,11 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } getGlobal: () => global, getHandler: () => handler, getUrl: () => url - }; + }, { + enabled: { + get: () => _enabled + } + }); function _track({ eventType, args }) { if (this.getAdapterType() === BUNDLE) { @@ -140,6 +144,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } this.enableAnalytics = function _enable() { return logMessage(`Analytics adapter for "${global}" already enabled, unnecessary call to \`enableAnalytics\`.`); }; + _enabled = true; } function _disable() { @@ -147,6 +152,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } events.off(event, handler); }); this.enableAnalytics = this._oldEnable ? this._oldEnable : _enable; + _enabled = false; } function _emptyQueue() { diff --git a/src/adapters/analytics/example.js b/libraries/analyticsAdapter/examples/example.js similarity index 85% rename from src/adapters/analytics/example.js rename to libraries/analyticsAdapter/examples/example.js index 1321612b688..c6907907b23 100644 --- a/src/adapters/analytics/example.js +++ b/libraries/analyticsAdapter/examples/example.js @@ -2,7 +2,7 @@ * example.js - analytics adapter for Example Analytics Library example */ -import adapter from '../../AnalyticsAdapter.js'; +import adapter from '../AnalyticsAdapter.js'; export default adapter( { diff --git a/src/adapters/analytics/example2.js b/libraries/analyticsAdapter/examples/example2.js similarity index 92% rename from src/adapters/analytics/example2.js rename to libraries/analyticsAdapter/examples/example2.js index eadf994ce36..d95a3f54283 100644 --- a/src/adapters/analytics/example2.js +++ b/libraries/analyticsAdapter/examples/example2.js @@ -5,7 +5,7 @@ import { ajax } from '../../../src/ajax.js'; * example2.js - analytics adapter for Example2 Analytics Endpoint example */ -import adapter from '../../AnalyticsAdapter.js'; +import adapter from '../AnalyticsAdapter.js'; const url = 'https://httpbin.org/post'; const analyticsType = 'endpoint'; diff --git a/src/adapters/analytics/libraries/example.js b/libraries/analyticsAdapter/examples/libraries/example.js similarity index 95% rename from src/adapters/analytics/libraries/example.js rename to libraries/analyticsAdapter/examples/libraries/example.js index 0d758fd5513..f2bfd612193 100644 --- a/src/adapters/analytics/libraries/example.js +++ b/libraries/analyticsAdapter/examples/libraries/example.js @@ -31,8 +31,8 @@ events.init(); // overwrite example object and handle 'on' callbacks window[window.ExampleAnalyticsGlobalObject] = example = utils.errorless(function() { if (arguments[0] && arguments[0] === 'on') { - var eventName = arguments[1] && arguments[1]; - var args = arguments[2] && arguments[2]; + var eventName = arguments[1]; + var args = arguments[2]; if (eventName && args) { if (eventName === 'bidAdjustment') { pbjsHandlers.onBidAdjustment.apply(this, [args]); diff --git a/src/adapters/analytics/libraries/example2.js b/libraries/analyticsAdapter/examples/libraries/example2.js similarity index 95% rename from src/adapters/analytics/libraries/example2.js rename to libraries/analyticsAdapter/examples/libraries/example2.js index 68e814b1417..9a7106a48e0 100644 --- a/src/adapters/analytics/libraries/example2.js +++ b/libraries/analyticsAdapter/examples/libraries/example2.js @@ -31,8 +31,8 @@ events.init(); // overwrite example object and handle 'on' callbacks window[window.ExampleAnalyticsGlobalObject2] = example = utils.errorless(function() { if (arguments[0] && arguments[0] === 'on') { - var eventName = arguments[1] && arguments[1]; - var args = arguments[2] && arguments[2]; + var eventName = arguments[1]; + var args = arguments[2]; if (eventName && args) { if (eventName === 'bidAdjustment') { pbjsHandlers.onBidAdjustment.apply(this, [args]); diff --git a/libraries/getOrigin/index.js b/libraries/getOrigin/index.js new file mode 100644 index 00000000000..c37a913db07 --- /dev/null +++ b/libraries/getOrigin/index.js @@ -0,0 +1,11 @@ +/** + * Returns the origin + */ +export function getOrigin() { + // IE10 does not have this property. https://gist.github.com/hbogs/7908703 + if (!window.location.origin) { + return window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''); + } else { + return window.location.origin; + } +} diff --git a/modules/.submodules.json b/modules/.submodules.json index 8d62f30d7c4..d3665dd38f4 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -1,75 +1,79 @@ { - "userId": [ - "33acrossIdSystem", - "admixerIdSystem", - "adtelligentIdSystem", - "akamaiDAPIdSystem", - "amxIdSystem", - "britepoolIdSystem", - "connectIdSystem", - "cpexIdSystem", - "criteoIdSystem", - "dacIdSystem", - "deepintentDpesIdSystem", - "dmdIdSystem", - "fabrickIdSystem", - "flocIdSystem", - "hadronIdSystem", - "haloIdSystem", - "id5IdSystem", - "ftrackIdSystem", - "identityLinkIdSystem", - "idxIdSystem", - "imuIdSystem", - "intentIqIdSystem", - "justIdSystem", - "kinessoIdSystem", - "liveIntentIdSystem", - "lotamePanoramaIdSystem", - "merkleIdSystem", - "mwOpenLinkIdSystem", - "naveggIdSystem", - "netIdSystem", - "nextrollIdSystem", - "novatiqIdSystem", - "parrableIdSystem", - "pubProvidedIdSystem", - "publinkIdSystem", - "quantcastIdSystem", - "sharedIdSystem", - "tapadIdSystem", - "trustpidSystem", - "uid2IdSystem", - "unifiedIdSystem", - "verizonMediaIdSystem", - "zeotapIdPlusIdSystem", - "adqueryIdSystem", - "gravitoIdSystem" - ], - "adpod": [ - "freeWheelAdserverVideo", - "dfpAdServerVideo" - ], - "rtdModule": [ - "airgridRtdProvider", - "akamaiDapRtdProvider", - "browsiRtdProvider", - "dgkeywordRtdProvider", - "geoedgeRtdProvider", - "hadronRtdProvider", - "haloRtdProvider", - "iasRtdProvider", - "jwplayerRtdProvider", - "medianetRtdProvider", - "optimeraRtdProvider", - "permutiveRtdProvider", - "reconciliationRtdProvider", - "sirdataRtdProvider", - "timeoutRtdProvider", - "weboramaRtdProvider" - ], - "fpdModule": [ - "enrichmentFpdModule", - "validationFpdModule" - ] -} + "parentModules": { + "userId": [ + "33acrossIdSystem", + "admixerIdSystem", + "adtelligentIdSystem", + "amxIdSystem", + "britepoolIdSystem", + "connectIdSystem", + "cpexIdSystem", + "criteoIdSystem", + "dacIdSystem", + "deepintentDpesIdSystem", + "dmdIdSystem", + "fabrickIdSystem", + "hadronIdSystem", + "id5IdSystem", + "ftrackIdSystem", + "identityLinkIdSystem", + "idxIdSystem", + "imuIdSystem", + "intentIqIdSystem", + "justIdSystem", + "kinessoIdSystem", + "liveIntentIdSystem", + "lotamePanoramaIdSystem", + "merkleIdSystem", + "mwOpenLinkIdSystem", + "naveggIdSystem", + "netIdSystem", + "novatiqIdSystem", + "oneKeyIdSystem", + "parrableIdSystem", + "pubProvidedIdSystem", + "publinkIdSystem", + "quantcastIdSystem", + "sharedIdSystem", + "tapadIdSystem", + "tncIdSystem", + "trustpidSystem", + "uid2IdSystem", + "unifiedIdSystem", + "verizonMediaIdSystem", + "zeotapIdPlusIdSystem", + "adqueryIdSystem", + "gravitoIdSystem" + ], + "adpod": [ + "freeWheelAdserverVideo", + "dfpAdServerVideo" + ], + "rtdModule": [ + "1plusXRtdProvider", + "airgridRtdProvider", + "akamaiDapRtdProvider", + "blueconicRtdProvider", + "browsiRtdProvider", + "dgkeywordRtdProvider", + "geoedgeRtdProvider", + "hadronRtdProvider", + "haloRtdProvider", + "iasRtdProvider", + "jwplayerRtdProvider", + "medianetRtdProvider", + "oneKeyRtdProvider", + "optimeraRtdProvider", + "permutiveRtdProvider", + "reconciliationRtdProvider", + "sirdataRtdProvider", + "timeoutRtdProvider", + "weboramaRtdProvider" + ], + "fpdModule": [ + "enrichmentFpdModule", + "validationFpdModule", + "topicsFpdModule" + ] + } +} \ No newline at end of file diff --git a/modules/1plusXRtdProvider.js b/modules/1plusXRtdProvider.js new file mode 100644 index 00000000000..9d010c8d298 --- /dev/null +++ b/modules/1plusXRtdProvider.js @@ -0,0 +1,251 @@ +import { submodule } from '../src/hook.js'; +import { config } from '../src/config.js'; +import { ajax } from '../src/ajax.js'; +import { + logMessage, logError, + deepAccess, mergeDeep, + isNumber, isArray, deepSetValue +} from '../src/utils.js'; + +// Constants +const REAL_TIME_MODULE = 'realTimeData'; +const MODULE_NAME = '1plusX'; +const ORTB2_NAME = '1plusX.com' +const PAPI_VERSION = 'v1.0'; +const LOG_PREFIX = '[1plusX RTD Module]: '; +const LEGACY_SITE_KEYWORDS_BIDDERS = ['appnexus']; +export const segtaxes = { + // cf. https://github.com/InteractiveAdvertisingBureau/openrtb/pull/108 + AUDIENCE: 526, + CONTENT: 527, +}; +// Functions +/** + * Extracts the parameters for 1plusX RTD module from the config object passed at instanciation + * @param {Object} moduleConfig Config object passed to the module + * @param {Object} reqBidsConfigObj Config object for the bidders; each adapter has its own entry + * @returns {Object} Extracted configuration parameters for the module + */ +export const extractConfig = (moduleConfig, reqBidsConfigObj) => { + // CustomerId + const customerId = deepAccess(moduleConfig, 'params.customerId'); + if (!customerId) { + throw new Error('Missing parameter customerId in moduleConfig'); + } + // Timeout + const tempTimeout = deepAccess(moduleConfig, 'params.timeout'); + const timeout = isNumber(tempTimeout) && tempTimeout > 300 ? tempTimeout : 1000; + + // Bidders + const biddersTemp = deepAccess(moduleConfig, 'params.bidders'); + if (!isArray(biddersTemp) || !biddersTemp.length) { + throw new Error('Missing parameter bidders in moduleConfig'); + } + + const adUnitBidders = reqBidsConfigObj.adUnits + .flatMap(({ bids }) => bids.map(({ bidder }) => bidder)) + .filter((e, i, a) => a.indexOf(e) === i); + if (!isArray(adUnitBidders) || !adUnitBidders.length) { + throw new Error('Missing parameter bidders in bidRequestConfig'); + } + + const bidders = biddersTemp.filter(bidder => adUnitBidders.includes(bidder)); + if (!bidders.length) { + throw new Error('No bidRequestConfig bidder found in moduleConfig bidders'); + } + + return { customerId, timeout, bidders }; +} + +/** + * Gets the URL of Profile Api from which targeting data will be fetched + * @param {Object} config + * @param {string} config.customerId + * @returns {string} URL to access 1plusX Profile API + */ +const getPapiUrl = ({ customerId }) => { + // https://[yourClientId].profiles.tagger.opecloud.com/[VERSION]/targeting?url= + const currentUrl = encodeURIComponent(window.location.href); + const papiUrl = `https://${customerId}.profiles.tagger.opecloud.com/${PAPI_VERSION}/targeting?url=${currentUrl}`; + return papiUrl; +} + +/** + * Fetches targeting data. It contains the audience segments & the contextual topics + * @param {string} papiUrl URL of profile API + * @returns {Promise} Promise object resolving with data fetched from Profile API + */ +const getTargetingDataFromPapi = (papiUrl) => { + return new Promise((resolve, reject) => { + const requestOptions = { + customHeaders: { + 'Accept': 'application/json' + } + } + const callbacks = { + success(responseText, response) { + resolve(JSON.parse(response.response)); + }, + error(error) { + reject(error); + } + }; + ajax(papiUrl, callbacks, null, requestOptions) + }) +} + +/** + * Prepares the update for the ORTB2 object + * @param {Object} targetingData Targeting data fetched from Profile API + * @param {string[]} segments Represents the audience segments of the user + * @param {string[]} topics Represents the topics of the page + * @returns {Object} Object describing the updates to make on bidder configs + */ +export const buildOrtb2Updates = ({ segments = [], topics = [] }, bidder) => { + // Currently appnexus bidAdapter doesn't support topics in `site.content.data.segment` + // Therefore, writing them in `site.keywords` until it's supported + // Other bidAdapters do fine with `site.content.data.segment` + const writeToLegacySiteKeywords = LEGACY_SITE_KEYWORDS_BIDDERS.includes(bidder); + if (writeToLegacySiteKeywords) { + const site = { + keywords: topics.join(',') + }; + return { site }; + } + + const userData = { + name: ORTB2_NAME, + segment: segments.map((segmentId) => ({ id: segmentId })) + }; + const siteContentData = { + name: ORTB2_NAME, + segment: topics.map((topicId) => ({ id: topicId })), + ext: { segtax: segtaxes.CONTENT } + } + return { userData, siteContentData }; +} + +/** + * Merges the targeting data with the existing config for bidder and updates + * @param {string} bidder Bidder for which to set config + * @param {Object} ortb2Updates Updates to be applied to bidder config + * @param {Object} bidderConfigs All current bidder configs + * @returns {Object} Updated bidder config + */ +export const updateBidderConfig = (bidder, ortb2Updates, bidderConfigs) => { + const { site, siteContentData, userData } = ortb2Updates; + const bidderConfigCopy = mergeDeep({}, bidderConfigs[bidder]); + + if (site) { + // Legacy : cf. comment on buildOrtb2Updates first lines + const currentSite = deepAccess(bidderConfigCopy, 'ortb2.site'); + const updatedSite = mergeDeep(currentSite, site); + deepSetValue(bidderConfigCopy, 'ortb2.site', updatedSite); + } + + if (siteContentData) { + const siteDataPath = 'ortb2.site.content.data'; + const currentSiteContentData = deepAccess(bidderConfigCopy, siteDataPath) || []; + const updatedSiteContentData = [ + ...currentSiteContentData.filter(({ name }) => name != siteContentData.name), + siteContentData + ]; + deepSetValue(bidderConfigCopy, siteDataPath, updatedSiteContentData); + } + + if (userData) { + const userDataPath = 'ortb2.user.data'; + const currentUserData = deepAccess(bidderConfigCopy, userDataPath) || []; + const updatedUserData = [ + ...currentUserData.filter(({ name }) => name != userData.name), + userData + ]; + deepSetValue(bidderConfigCopy, userDataPath, updatedUserData); + } + + return bidderConfigCopy; +}; + +const setAppnexusAudiences = (audiences) => { + config.setConfig({ + appnexusAuctionKeywords: { + '1plusX': audiences, + }, + }); +} + +/** + * Updates bidder configs with the targeting data retreived from Profile API + * @param {Object} papiResponse Response from Profile API + * @param {Object} config Module configuration + * @param {string[]} config.bidders Bidders specified in module's configuration + */ +export const setTargetingDataToConfig = (papiResponse, { bidders }) => { + const bidderConfigs = config.getBidderConfig(); + const { s: segments, t: topics } = papiResponse; + + for (const bidder of bidders) { + const ortb2Updates = buildOrtb2Updates({ segments, topics }, bidder); + const updatedBidderConfig = updateBidderConfig(bidder, ortb2Updates, bidderConfigs); + if (updatedBidderConfig) { + config.setBidderConfig({ + bidders: [bidder], + config: updatedBidderConfig + }); + } + if (bidder === 'appnexus') { + // Do the legacy stuff for appnexus with segments + setAppnexusAudiences(segments); + } + } +} + +// Functions exported in submodule object +/** + * Init + * @param {Object} config Module configuration + * @param {boolean} userConsent + * @returns true + */ +const init = (config, userConsent) => { + return true; +} + +/** + * + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Function} callback Called on completion + * @param {Object} moduleConfig Configuration for 1plusX RTD module + * @param {boolean} userConsent + */ +const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent) => { + try { + // Get the required config + const { customerId, bidders } = extractConfig(moduleConfig, reqBidsConfigObj); + // Get PAPI URL + const papiUrl = getPapiUrl({ customerId }) + // Call PAPI + getTargetingDataFromPapi(papiUrl) + .then((papiResponse) => { + logMessage(LOG_PREFIX, 'Get targeting data request successful'); + setTargetingDataToConfig(papiResponse, { bidders }); + callback(); + }) + .catch((error) => { + throw error; + }) + } catch (error) { + logError(LOG_PREFIX, error); + callback(); + } +} + +// The RTD submodule object to be exported +export const onePlusXSubmodule = { + name: MODULE_NAME, + init, + getBidRequestData +} + +// Register the onePlusXSubmodule as submodule of realTimeData +submodule(REAL_TIME_MODULE, onePlusXSubmodule); diff --git a/modules/1plusXRtdProvider.md b/modules/1plusXRtdProvider.md new file mode 100644 index 00000000000..6a6211b37cc --- /dev/null +++ b/modules/1plusXRtdProvider.md @@ -0,0 +1,65 @@ +# 1plusX Real-time Data Submodule + +## Overview + + Module Name: 1plusX Rtd Provider + Module Type: Rtd Provider + Maintainer: dc-team-1px@triplelift.com + +## Description + +The 1plusX RTD module appends User and Contextual segments to the bidding object. + +## Usage + +### Build +``` +gulp build --modules="rtdModule,1plusXRtdProvider,appnexusBidAdapter,..." +``` + +> Note that the global RTD module, `rtdModule`, is a prerequisite of the 1plusX RTD module. + +### Configuration + +Use `setConfig` to instruct Prebid.js to initilize the 1plusX RTD module, as specified below. + +This module is configured as part of the `realTimeData.dataProviders` + +```javascript +var TIMEOUT = 1000; +pbjs.setConfig({ + realTimeData: { + auctionDelay: TIMEOUT, + dataProviders: [{ + name: '1plusX', + waitForIt: true, + params: { + customerId: 'acme', + bidders: ['appnexus', 'rubicon'], + timeout: TIMEOUT + } + }] + } +}); +``` + +### Parameters + +| Name | Type | Description | Default | +| :---------------- | :------------ | :--------------------------------------------------------------- |:----------------- | +| name | String | Real time data module name | Always '1plusX' | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | +| params | Object | | | +| params.customerId | String | Your 1plusX customer id | | +| params.bidders | Array | List of bidders for which you would like data to be set | | +| params.timeout | Integer | timeout (ms) | 1000ms | + +## Testing + +To view an example of how the 1plusX RTD module works : + +`gulp serve --modules=rtdModule,1plusXRtdProvider,appnexusBidAdapter,rubiconBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/1plusXRtdProvider_example.html` diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 498e6cf8634..89c00897571 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -16,6 +16,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; // **************************** UTILS *************************** // const BIDDER_CODE = '33across'; +const BIDDER_ALIASES = ['33across_mgni']; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; const SYNC_ENDPOINT = 'https://ssc-cms.33across.com/ps/?m=xch&rt=html&ru=deb'; @@ -70,7 +71,9 @@ function isBidRequestValid(bid) { } function _validateBasic(bid) { - if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + const invalidBidderName = bid.bidder !== BIDDER_CODE && !BIDDER_ALIASES.includes(bid.bidder); + + if (invalidBidderName || !bid.params) { return false; } @@ -195,7 +198,7 @@ function _buildRequestParams(bidRequests, bidderRequest) { const uspConsent = bidderRequest && bidderRequest.uspConsent; - const pageUrl = (bidderRequest && bidderRequest.refererInfo) ? (bidderRequest.refererInfo.referer) : (undefined); + const pageUrl = bidderRequest?.refererInfo?.page adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques); @@ -251,6 +254,7 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU }); ttxRequest.site = { id: siteId }; + ttxRequest.device = _buildDeviceORTB(); if (pageUrl) { ttxRequest.site.page = pageUrl; @@ -318,7 +322,7 @@ function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageU 'url': url, 'data': JSON.stringify(ttxRequest), 'options': options - } + }; } // BUILD REQUESTS: SET EXTENSIONS @@ -330,12 +334,15 @@ function setExtensions(obj = {}, extFields) { // BUILD REQUESTS: IMP function _buildImpORTB(bidRequest) { + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + const imp = { id: bidRequest.bidId, ext: { ttx: { prod: deepAccess(bidRequest, 'params.productId') - } + }, + ...(gpid ? { gpid } : {}) } }; @@ -431,7 +438,7 @@ function _buildBannerORTB(bidRequest) { return { format, ext - } + }; } // BUILD REQUESTS: VIDEO @@ -445,7 +452,7 @@ function _buildVideoORTB(bidRequest) { ...videoBidderParams // Bidder Specific overrides }; - const video = {} + const video = {}; const { w, h } = _getSize(videoParams.playerSize[0]); video.w = w; @@ -731,10 +738,79 @@ function _createSync({ siteId = 'zzz000000000003zzz', gdprConsent = {}, uspConse return sync; } +// BUILD REQUESTS: DEVICE +function _buildDeviceORTB() { + const win = getWindowSelf(); + + return { + ext: { + ttx: { + ...getScreenDimensions(), + pxr: win.devicePixelRatio, + vp: getViewportDimensions(), + ah: win.screen.availHeight, + mtp: win.navigator.maxTouchPoints + } + } + }; +} + +function getTopMostAccessibleWindow() { + let mostAccessibleWindow = getWindowSelf(); + + try { + while (mostAccessibleWindow.parent !== mostAccessibleWindow && + mostAccessibleWindow.parent.document) { + mostAccessibleWindow = mostAccessibleWindow.parent; + } + } catch (err) { + // Do not throw an exception if we can't access the topmost frame. + } + + return mostAccessibleWindow; +} + +function getViewportDimensions() { + const topWin = getTopMostAccessibleWindow(); + const documentElement = topWin.document.documentElement; + + return { + w: documentElement.clientWidth, + h: documentElement.clientHeight, + }; +} + +function getScreenDimensions() { + const { + innerWidth: windowWidth, + innerHeight: windowHeight, + screen + } = getWindowSelf(); + + const [biggerDimension, smallerDimension] = [ + Math.max(screen.width, screen.height), + Math.min(screen.width, screen.height), + ]; + + if (windowHeight > windowWidth) { // Portrait mode + return { + w: smallerDimension, + h: biggerDimension, + }; + } + + // Landscape mode + return { + w: biggerDimension, + h: smallerDimension, + }; +} + export const spec = { NON_MEASURABLE, code: BIDDER_CODE, + aliases: BIDDER_ALIASES, supportedMediaTypes: [ BANNER, VIDEO ], gvlid: GVLID, isBidRequestValid, diff --git a/modules/7xbidBidAdapter.md b/modules/7xbidBidAdapter.md deleted file mode 100644 index 692428332f0..00000000000 --- a/modules/7xbidBidAdapter.md +++ /dev/null @@ -1,59 +0,0 @@ -# Overview - -Module Name: 7xbid Bid Adapter - -Maintainer: 7xbid.com@gmail.com - -# Description - -Module that connects to 7xbid's demand sources - -# Test Parameters -```javascript - var adUnits = [ - { - code: 'test', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [ - { - bidder: '7xbid', - params: { - placementId: 1425292, - currency: 'USD' - } - } - ] - }, - { - code: 'test', - mediaTypes: { - native: { - title: { - required: true, - len: 80 - }, - image: { - required: true, - sizes: [150, 50] - }, - sponsoredBy: { - required: true - } - } - }, - bids: [ - { - bidder: '7xbid', - params: { - placementId: 1429695, - currency: 'USD' - } - }, - ], - } - ]; -``` \ No newline at end of file diff --git a/modules/a4gBidAdapter.js b/modules/a4gBidAdapter.js index 03f9d6fd726..f0c7a5f5af1 100644 --- a/modules/a4gBidAdapter.js +++ b/modules/a4gBidAdapter.js @@ -44,7 +44,7 @@ export const spec = { let data = { [IFRAME_PARAM_NAME]: 0, - [LOCATION_PARAM_NAME]: (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : window.location.href, + [LOCATION_PARAM_NAME]: bidderRequest.refererInfo?.page, [SIZE_PARAM_NAME]: sizeParams.join(ARRAY_PARAM_SEPARATOR), [ID_PARAM_NAME]: idParams.join(ARRAY_PARAM_SEPARATOR), [ZONE_ID_PARAM_NAME]: zoneIds.join(ARRAY_PARAM_SEPARATOR) diff --git a/modules/aardvarkBidAdapter.md b/modules/aardvarkBidAdapter.md deleted file mode 100644 index 9f7a128b6f3..00000000000 --- a/modules/aardvarkBidAdapter.md +++ /dev/null @@ -1,30 +0,0 @@ -# Overview - -**Module Name**: Aardvark Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: chris@rtk.io - -# Description - -Module that connects to a RTK.io Ad Units to fetch bids. - -# Test Parameters -``` - var adUnits = [{ - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - code: 'div-gpt-ad-1460505748561-0', - - bids: [{ - bidder: 'aardvark', - params: { - ai: '0000', - sc: '1234' - } - }] - - }]; -``` diff --git a/modules/ablidaBidAdapter.js b/modules/ablidaBidAdapter.js index cb4f4ef2724..ca489a10a90 100644 --- a/modules/ablidaBidAdapter.js +++ b/modules/ablidaBidAdapter.js @@ -2,6 +2,7 @@ import { triggerPixel } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'ablida'; const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; @@ -28,6 +29,9 @@ export const spec = { * @param bidderRequest */ buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + if (validBidRequests.length === 0) { return []; } @@ -45,7 +49,8 @@ export const spec = { sizes: sizes, bidId: bidRequest.bidId, categories: bidRequest.params.categories, - referer: bidderRequest.refererInfo.referer, + // TODO: should referer be 'ref'? + referer: bidderRequest.refererInfo.page, jaySupported: jaySupported, device: device, adapterVersion: 5, diff --git a/modules/acuityAdsBidAdapter.js b/modules/acuityAdsBidAdapter.js new file mode 100644 index 00000000000..f469fe48c60 --- /dev/null +++ b/modules/acuityAdsBidAdapter.js @@ -0,0 +1,207 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'acuityads'; +const AD_URL = 'https://prebid.admanmedia.com/pbjs'; +const SYNC_URL = 'https://cs.admanmedia.com'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + placement.placementId = placementId; + placement.type = 'publisher'; + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && params.placementId); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + // TODO: does the fallback make sense here? + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/acuityAdsBidAdapter.md b/modules/acuityAdsBidAdapter.md new file mode 100644 index 00000000000..a19e0a6b0ba --- /dev/null +++ b/modules/acuityAdsBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: AcuityAds Bidder Adapter +Module Type: AcuityAds Bidder Adapter +Maintainer: sa-support@brightcom.com +``` + +# Description + +Connects to AcuityAds exchange for bids. +AcuityAds bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'acuityads', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'acuityads', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'acuityads', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/adWMGAnalyticsAdapter.js b/modules/adWMGAnalyticsAdapter.js index 8183187eb73..dd0340071d1 100644 --- a/modules/adWMGAnalyticsAdapter.js +++ b/modules/adWMGAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; diff --git a/modules/adWMGBidAdapter.js b/modules/adWMGBidAdapter.js index 7bf6c703a55..12dc36d694c 100644 --- a/modules/adWMGBidAdapter.js +++ b/modules/adWMGBidAdapter.js @@ -27,9 +27,10 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { const timeout = bidderRequest.timeout || 0; const debug = config.getConfig('debug') || false; - const referrer = bidderRequest.refererInfo.referer; + // TODO: is 'page' the right value here? + const referrer = bidderRequest.refererInfo.page; const locale = window.navigator.language && window.navigator.language.length > 0 ? window.navigator.language.substr(0, 2) : ''; - const domain = config.getConfig('publisherDomain') || (window.location && window.location.host ? window.location.host : ''); + const domain = bidderRequest.refererInfo.domain || ''; const ua = window.navigator.userAgent.toLowerCase(); const additional = spec.parseUserAgent(ua); diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index f929f7e660b..96f3f089cbd 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -2,7 +2,7 @@ * Analytics Adapter for Adagio */ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { getWindowTop } from '../src/utils.js'; diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index b98567878a8..ac01b317ab3 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -19,19 +19,19 @@ import { logInfo, logWarn, mergeDeep, - parseUrl } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {loadExternalScript} from '../src/adloader.js'; import {verify} from 'criteo-direct-rsa-validate/build/verify.js'; import {getStorageManager} from '../src/storageManager.js'; -import {getRefererInfo} from '../src/refererDetection.js'; +import {getRefererInfo, parseDomain} from '../src/refererDetection.js'; import {createEidsArray} from './userId/eids.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {OUTSTREAM} from '../src/video.js'; import { getGlobal } from '../src/prebidGlobal.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'adagio'; const LOG_PREFIX = 'Adagio:'; @@ -270,12 +270,10 @@ function getDevice() { function getSite(bidderRequest) { const { refererInfo } = bidderRequest; - const url = parseUrl(refererInfo.referer); - return { - domain: url.hostname || '', - page: refererInfo.referer || '', - referrer: canAccessTopWindow() ? getWindowTop().document.referrer || '' : getWindowSelf().document.referrer || '', + domain: parseDomain(refererInfo.topmostLocation) || '', + page: refererInfo.topmostLocation || '', + referrer: refererInfo.ref || getWindowSelf().document.referrer || '', top: refererInfo.reachedTop }; }; @@ -621,7 +619,7 @@ export function setExtraParam(bid, paramName) { } const adgGlobalConf = config.getConfig('adagio') || {}; - const ortb2Conf = config.getConfig('ortb2'); + const ortb2Conf = bid.ortb2; const detected = adgGlobalConf[paramName] || deepAccess(ortb2Conf, `site.ext.data.${paramName}`, null); if (detected) { @@ -884,6 +882,9 @@ export const spec = { }, buildRequests(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const secure = (location.protocol === 'https:') ? 1 : 0; const device = internal.getDevice(); const site = internal.getSite(bidderRequest); @@ -1122,7 +1123,7 @@ export const spec = { ...globalFeatures, print_number: getPrintNumber(adagioBid.adUnitCode, adagioBidderRequest).toString(), adunit_position: getSlotPosition(adagioBid.params.adUnitElementId) // adUnitElementId à déplacer ??? - } + }; adagioBid.params.pageviewId = internal.getPageviewId(); adagioBid.params.prebidVersion = '$prebid.version$'; diff --git a/modules/adblender.md b/modules/adblender.md deleted file mode 100644 index e70b2a4a8ed..00000000000 --- a/modules/adblender.md +++ /dev/null @@ -1,36 +0,0 @@ -# Overview - -Module Name: AdBlender Bidder Adapter -Module Type: Bidder Adapter -Maintainer: contact@ad-blender.com - -# Description - -Connects to AdBlender demand source to fetch bids. -Banner and Video formats are supported. -Please use ```adblender``` as the bidder code. -#Bidder Config -You can set an alternate endpoint url `pbjs.setBidderConfig` for the bidder `adblender` -``` -pbjs.setBidderConfig({ - bidders: ["adblender"], - config: {"adblender": { "endpoint_url": "https://inv-nets.admixer.net/adblender.1.1.aspx"}} - }) -``` -# Ad Unit Example -``` - var adUnits = [ - { - code: 'desktop-banner-ad-div', - sizes: [[300, 250]], // a display size - bids: [ - { - bidder: "adblender", - params: { - zone: 'fb3d34d0-7a88-4a4a-a5c9-8088cd7845f4' - } - } - ] - } - ]; -``` diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js index de8a3598be1..917d18e9fae 100644 --- a/modules/adbookpspBidAdapter.js +++ b/modules/adbookpspBidAdapter.js @@ -19,6 +19,7 @@ import { uniques } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; /** * CONSTANTS @@ -101,6 +102,9 @@ function isBidRequestValid(bidRequest) { } function buildRequests(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const requests = []; if (validBidRequests.length > 0) { @@ -123,9 +127,9 @@ function buildRequest(validBidRequests, bidderRequest) { id: bidderRequest.bidderRequestId, tmax: bidderRequest.timeout, site: { - domain: window.location.hostname, - page: window.location.href, - ref: bidderRequest.refererInfo.referer, + domain: bidderRequest.refererInfo.domain, + page: bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref, }, source: buildSource(validBidRequests, bidderRequest), device: buildDevice(), diff --git a/modules/adbutlerBidAdapter.md b/modules/adbutlerBidAdapter.md deleted file mode 100644 index 1921cc4046e..00000000000 --- a/modules/adbutlerBidAdapter.md +++ /dev/null @@ -1,34 +0,0 @@ -# Overview - -**Module Name**: AdButler Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: dan@sparklit.com - -# Description - -Module that connects to an AdButler zone to fetch bids. - -# Test Parameters -``` - var adUnits = [ - { - code: 'display-div', - sizes: [[300, 250]], // a display size - bids: [ - { - bidder: "adbutler", - params: { - accountID: '167283', - zoneID: '210093', - keyword: 'red', //optional - minCPM: '1.00', //optional - maxCPM: '5.00' //optional - extra: { // optional - foo: "bar" - } - } - } - ] - } - ]; -``` diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js index dcc453ef35a..f0a6852b084 100644 --- a/modules/addefendBidAdapter.js +++ b/modules/addefendBidAdapter.js @@ -21,7 +21,8 @@ export const spec = { pageId: false, gdpr_applies: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : 'true', gdpr_consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : '', - referer: bidderRequest.refererInfo.referer, + // TODO: is 'page' the correct item here? + referer: bidderRequest.refererInfo.page, bids: [], }; diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index e21c4219baf..a3fb128437b 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -1,15 +1,12 @@ // jshint esversion: 6, es3: false, node: true 'use strict'; -import { - registerBidder -} from '../src/adapters/bidderFactory.js'; -import { - NATIVE, BANNER, VIDEO -} from '../src/mediaTypes.js'; -import { mergeDeep, _map, deepAccess, parseSizesInput, deepSetValue } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { Renderer } from '../src/Renderer.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {_map, deepAccess, deepSetValue, mergeDeep, parseSizesInput} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {Renderer} from '../src/Renderer.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const { getConfig } = config; @@ -64,9 +61,12 @@ export const spec = { return !!(mid || (inv && mname)); }, buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let app, site; - const commonFpd = getConfig('ortb2') || {}; + const commonFpd = bidderRequest.ortb2 || {}; let { user } = commonFpd; if (typeof getConfig('app') === 'object') { @@ -81,7 +81,7 @@ export const spec = { } if (!site.page) { - site.page = bidderRequest.refererInfo.referer; + site.page = bidderRequest.refererInfo.page; } } diff --git a/modules/adfinityBidAdapter.md b/modules/adfinityBidAdapter.md deleted file mode 100644 index f67d4fddfe7..00000000000 --- a/modules/adfinityBidAdapter.md +++ /dev/null @@ -1,67 +0,0 @@ -# Overview - -``` -Module Name: Adfinity Bidder Adapter -Module Type: Bidder Adapter -Maintainer: adfinity_prebid@i.ua -``` - -# Description - -Module that connects to Adfinity demand sources - -# Test Parameters -``` - var adUnits = [ - { - code: 'placementid_0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: 'adfinity', - params: { - placement_id: 0, - traffic: 'banner' - } - } - ] - }, - { - code: 'placementid_0', - mediaTypes: { - native: { - - } - }, - bids: [ - { - bidder: 'adfinity', - params: { - placement_id: 0, - traffic: 'native' - } - } - ] - }, - { - code: 'placementid_0', - mediaTypes: { - video: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [ - { - bidder: 'adfinity', - params: { - placement_id: 0, - traffic: 'video' - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index e0d3a881cad..0e4e9ef6805 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -1,7 +1,8 @@ -import {tryAppendQueryString, getBidIdParameter} from '../src/utils.js'; +import {tryAppendQueryString, getBidIdParameter, escapeUnsafeChars} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const ADG_BIDDER_CODE = 'adgeneration'; @@ -25,13 +26,18 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - const ADGENE_PREBID_VERSION = '1.3.0'; + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const ADGENE_PREBID_VERSION = '1.4.0'; let serverRequests = []; for (let i = 0, len = validBidRequests.length; i < len; i++) { const validReq = validBidRequests[i]; const DEBUG_URL = 'https://api-test.scaleout.jp/adsv/v1'; const URL = 'https://d.socdm.com/adsv/v1'; const url = validReq.params.debug ? DEBUG_URL : URL; + const criteoId = getCriteoId(validReq); + const id5id = getId5Id(validReq); + const id5LinkType = getId5LinkType(validReq); let data = ``; data = tryAppendQueryString(data, 'posall', 'SSPLOC'); const id = getBidIdParameter('id', validReq.params); @@ -45,11 +51,16 @@ export const spec = { data = tryAppendQueryString(data, 'pbver', '$prebid.version$'); data = tryAppendQueryString(data, 'sdkname', 'prebidjs'); data = tryAppendQueryString(data, 'adapterver', ADGENE_PREBID_VERSION); + data = tryAppendQueryString(data, 'adgext_criteo_id', criteoId); + data = tryAppendQueryString(data, 'adgext_id5_id', id5id); + data = tryAppendQueryString(data, 'adgext_id5_id_link_type', id5LinkType); // native以外にvideo等の対応が入った場合は要修正 if (!validReq.mediaTypes || !validReq.mediaTypes.native) { data = tryAppendQueryString(data, 'imark', '1'); } - data = tryAppendQueryString(data, 'tp', bidderRequest.refererInfo.referer); + + // TODO: is 'page' the right value here? + data = tryAppendQueryString(data, 'tp', bidderRequest.refererInfo.page); if (isIos()) { const hyperId = getHyperId(validReq); if (hyperId != null) { @@ -189,7 +200,7 @@ function createNativeAd(body) { native.clickTrackers = body.native_ad.link.clicktrackers || []; native.impressionTrackers = body.native_ad.imptrackers || []; if (body.beaconurl && body.beaconurl != '') { - native.impressionTrackers.push(body.beaconurl) + native.impressionTrackers.push(body.beaconurl); } } return native; @@ -219,7 +230,7 @@ function insertVASTMethodForAPV(targetId, vastXml) { }; let script = document.createElement(`script`); script.type = 'text/javascript'; - script.innerHTML = `(function(){ new APV.VideoAd(${JSON.stringify(apvVideoAdParam)}).load('${vastXml.replace(/\r?\n/g, '')}'); })();`; + script.innerHTML = `(function(){ new APV.VideoAd(${escapeUnsafeChars(JSON.stringify(apvVideoAdParam))}).load('${vastXml.replace(/\r?\n/g, '')}'); })();`; return script.outerHTML; } @@ -274,6 +285,22 @@ function getCurrencyType() { * @param validReq request * @return {null|string} */ +function getCriteoId(validReq) { + return (validReq.userId && validReq.userId.criteoId) ? validReq.userId.criteoId : null +} + +function getId5Id(validReq) { + return validId5(validReq) ? validReq.userId.id5id.uid : null +} + +function getId5LinkType(validReq) { + return validId5(validReq) ? validReq.userId.id5id.ext.linkType : null +} + +function validId5(validReq) { + return validReq.userId && validReq.userId.id5id && validReq.userId.id5id.uid && validReq.userId.id5id.ext.linkType +} + function getHyperId(validReq) { if (validReq.userId && validReq.userId.novatiq && validReq.userId.novatiq.snowflake.syncResponse === 1) { return validReq.userId.novatiq.snowflake.id; diff --git a/modules/adglareBidAdapter.md b/modules/adglareBidAdapter.md deleted file mode 100644 index 845564473c7..00000000000 --- a/modules/adglareBidAdapter.md +++ /dev/null @@ -1,36 +0,0 @@ -# Overview - -``` -Module Name: AdGlare Ad Server Adapter -Module Type: Bidder Adapter -Maintainer: prebid@adglare.com -``` - -# Description - -Adapter that connects to your AdGlare Ad Server. -Including support for your white label ad serving domain. - -# Test Parameters -``` - var adUnits = [ - { - code: 'your-div-id', - mediaTypes: { - banner: { - sizes: [[300,250], [728,90]], - } - }, - bids: [ - { - bidder: 'adglare', - params: { - domain: 'try.engine.adglare.net', - zID: '475579334', - type: 'banner' - } - } - ] - } - ]; -``` diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js index 7f5af047993..977d161b214 100644 --- a/modules/adhashBidAdapter.js +++ b/modules/adhashBidAdapter.js @@ -103,7 +103,8 @@ export const spec = { const bidRequests = []; let referrer = ''; if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.referer; + // TODO: is 'page' the right value here? + referrer = bidderRequest.refererInfo.page; } for (var i = 0; i < validBidRequests.length; i++) { var index = Math.floor(Math.random() * validBidRequests[i].sizes.length); diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 145b5605bc2..2d1426a2cda 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -26,7 +26,8 @@ export const spec = { const adheseConfig = config.getConfig('adhese'); const gdprParams = (gdprConsent && gdprConsent.consentString) ? { xt: [gdprConsent.consentString] } : {}; - const refererParams = (refererInfo && refererInfo.referer) ? { xf: [base64urlEncode(refererInfo.referer)] } : {}; + // TODO: is 'page' the right value here? + const refererParams = (refererInfo && refererInfo.page) ? { xf: [base64urlEncode(refererInfo.page)] } : {}; const globalCustomParams = (adheseConfig && adheseConfig.globalTargets) ? cleanTargets(adheseConfig.globalTargets) : {}; const commonParams = { ...globalCustomParams, ...gdprParams, ...refererParams }; const vastContentAsUrl = !(adheseConfig && adheseConfig.vastContentAsUrl == false); diff --git a/modules/adkernelAdnAnalyticsAdapter.js b/modules/adkernelAdnAnalyticsAdapter.js index de5d59ca6f8..901c0d2fd98 100644 --- a/modules/adkernelAdnAnalyticsAdapter.js +++ b/modules/adkernelAdnAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import { logError, parseUrl, _each } from '../src/utils.js'; @@ -381,6 +381,7 @@ export function ExpiringQueue(callback, ttl) { } } +// TODO: this should reuse logic from refererDetection function getNavigationInfo() { try { return getLocationAndReferrer(self.top); diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 39f7b9fd2b2..4612310a9a4 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, parseSizesInput, isArray, deepSetValue, parseUrl, isStr, isNumber, logInfo } from '../src/utils.js'; +import { deepAccess, parseSizesInput, isArray, deepSetValue, isStr, isNumber, logInfo } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -10,7 +10,7 @@ const DEFAULT_APIS = [1, 2]; const GVLID = 14; function isRtbDebugEnabled(refInfo) { - return refInfo.referer.indexOf('adk_debug=true') !== -1; + return refInfo.topmostLocation?.indexOf('adk_debug=true') !== -1; } function buildImp(bidRequest) { @@ -83,13 +83,10 @@ function buildRequestParams(tags, bidderRequest) { } function buildSite(refInfo) { - let loc = parseUrl(refInfo.referer); - let result = { - page: `${loc.protocol}://${loc.hostname}${loc.pathname}`, - secure: ~~(loc.protocol === 'https') - }; - if (self === top && document.referrer) { - result.ref = document.referrer; + const result = { + page: refInfo.page, + secure: ~~(refInfo.page && refInfo.page.startsWith('https')), + ref: refInfo.ref } let keywords = document.getElementsByTagName('meta')['keywords']; if (keywords && keywords.content) { diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index c2d6ca4d4dd..ff7d3be6ebf 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -7,7 +7,6 @@ import { deepSetValue, getAdUnitSizes, getDNT, - inIframe, isArray, isArrayOfNums, isEmpty, @@ -16,12 +15,13 @@ import { isStr, mergeDeep, parseGPTSingleSizeArrayToRtbSize, - parseUrl + getDefinedParams } from '../src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {find, includes} from '../src/polyfill.js'; +import {find} from '../src/polyfill.js'; import {config} from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; /* * In case you're AdKernel whitelable platform's client who needs branded adapter to @@ -29,10 +29,11 @@ import {config} from '../src/config.js'; * * Please contact prebid@adkernel.com and we'll add your adapter as an alias. */ - -const VIDEO_TARGETING = Object.freeze(['mimes', 'minduration', 'maxduration', 'protocols', - 'startdelay', 'linearity', 'boxingallowed', 'playbackmethod', 'delivery', - 'pos', 'api', 'ext']); +const VIDEO_PARAMS = ['pos', 'context', 'placement', 'api', 'mimes', 'protocols', 'playbackmethod', 'minduration', 'maxduration', + 'startdelay', 'linearity', 'skip', 'skipmin', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackend', 'boxingallowed']; +const VIDEO_FPD = ['battr', 'pos']; +const NATIVE_FPD = ['battr', 'api']; +const BANNER_FPD = ['btype', 'battr', 'pos', 'api']; const VERSION = '1.6'; const SYNC_IFRAME = 1; const SYNC_IMAGE = 2; @@ -94,7 +95,9 @@ export const spec = { {code: 'ergadx'}, {code: 'turktelekom'}, {code: 'felixads'}, - {code: 'motionspots'} + {code: 'motionspots'}, + {code: 'sonic_twist'}, + {code: 'displayioads'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -120,6 +123,9 @@ export const spec = { * @returns {ServerRequest[]} */ buildRequests: function (bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + let impGroups = groupImpressionsByHostZone(bidRequests, bidderRequest.refererInfo); let requests = []; let schain = bidRequests[0].schain; @@ -226,7 +232,7 @@ registerBidder(spec); * @param refererInfo {refererInfo} */ function groupImpressionsByHostZone(bidRequests, refererInfo) { - let secure = (refererInfo && refererInfo.referer.indexOf('https:') === 0); + let secure = (refererInfo && refererInfo.page?.indexOf('https:') === 0); return Object.values( bidRequests.map(bidRequest => buildImp(bidRequest, secure)) .reduce((acc, curr, index) => { @@ -271,18 +277,18 @@ function buildImp(bidRequest, secure) { format: sizes.map(wh => parseGPTSingleSizeArrayToRtbSize(wh)), topframe: 0 }; + populateImpFpd(imp.banner, bidRequest, BANNER_FPD); mediaType = BANNER; } else if (deepAccess(bidRequest, 'mediaTypes.video')) { let video = deepAccess(bidRequest, 'mediaTypes.video'); - imp.video = {}; + imp.video = getDefinedParams(video, VIDEO_PARAMS); + populateImpFpd(imp.video, bidRequest, VIDEO_FPD); if (video.playerSize) { sizes = video.playerSize[0]; imp.video = Object.assign(imp.video, parseGPTSingleSizeArrayToRtbSize(sizes) || {}); - } - if (bidRequest.params.video) { - Object.keys(bidRequest.params.video) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => imp.video[key] = bidRequest.params.video[key]); + } else if (video.w && video.h) { + imp.video.w = video.w; + imp.video.h = video.h; } mediaType = VIDEO; } else if (deepAccess(bidRequest, 'mediaTypes.native')) { @@ -291,6 +297,7 @@ function buildImp(bidRequest, secure) { ver: '1.1', request: JSON.stringify(nativeRequest) }; + populateImpFpd(imp.native, bidRequest, NATIVE_FPD); mediaType = NATIVE; } else { throw new Error('Unsupported bid received'); @@ -334,6 +341,19 @@ function buildNativeRequest(nativeReq) { return request; } +/** + * Populate impression-level FPD from bid request + * @param target {Object} + * @param bidRequest {BidRequest} + * @param props {String[]} + */ +function populateImpFpd(target, bidRequest, props) { + if (bidRequest.ortb2Imp === undefined) { + return; + } + Object.assign(target, getDefinedParams(bidRequest.ortb2Imp, props)); +} + /** * Builds image asset request */ @@ -506,7 +526,7 @@ function makeSyncInfo(bidderRequest) { * @return {Object} Complete rtb request */ function buildRtbRequest(imps, bidderRequest, schain) { - let fpd = config.getConfig('ortb2') || {}; + let fpd = bidderRequest.ortb2 || {}; let req = mergeDeep( makeBaseRequest(bidderRequest, imps, fpd), @@ -535,14 +555,13 @@ function getLanguage() { * Creates site description object */ function createSite(refInfo, fpd) { - let url = parseUrl(refInfo.referer); let site = { - 'domain': url.hostname, - 'page': `${url.protocol}://${url.hostname}${url.pathname}` + 'domain': refInfo.domain, + 'page': refInfo.page }; mergeDeep(site, fpd.site); - if (!inIframe() && document.referrer) { - site.ref = document.referrer; + if (refInfo.ref != null) { + site.ref = refInfo.ref; } else { delete site.ref; } diff --git a/modules/adliveBidAdapter.md b/modules/adliveBidAdapter.md deleted file mode 100644 index 4fc6a112e82..00000000000 --- a/modules/adliveBidAdapter.md +++ /dev/null @@ -1,28 +0,0 @@ -# Overview -``` -Module Name: Adlive Bid Adapter -Module Type: Bidder Adapter -Maintainer: traffic@adlive.io -``` - -# Description -Module that connects to Adlive's server for bids. -Currently module supports only banner mediaType. - -# Test Parameters -``` - var adUnits = [{ - code: '/test/div', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [{ - bidder: 'adlive', - params: { - hashes: ['1e100887dd614b0909bf6c49ba7f69fdd1360437'] - } - }] - }]; -``` \ No newline at end of file diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 095fb917597..5db75c656bb 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -5,7 +5,7 @@ */ import adapterManager from '../src/adapterManager.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import {loadExternalScript} from '../src/adloader.js'; import {auctionManager} from '../src/auctionManager.js'; import {AUCTION_COMPLETED} from '../src/auction.js'; @@ -61,7 +61,7 @@ MACRO['creatype'] = function(b, c) { }; MACRO['pageurl'] = function(b, c) { const refererInfo = getRefererInfo(); - return (refererInfo.canonicalUrl || refererInfo.referer || '').substr(0, 300).split(/[?#]/)[0]; + return (refererInfo.page || '').substr(0, 300).split(/[?#]/)[0]; }; MACRO['gpid'] = function(b, c) { const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index 489fadf91f4..8862ac8ac47 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -12,7 +12,6 @@ /* eslint prebid/validate-imports: "off" */ import {command as analyticsCommand, COMMAND} from './adlooxAnalyticsAdapter.js'; -import {config as _config} from '../src/config.js'; import {submodule} from '../src/hook.js'; import {ajax} from '../src/ajax.js'; import {getGlobal} from '../src/prebidGlobal.js'; @@ -229,9 +228,9 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { return config.params.thresholds.filter(t => t <= v); } - const ortb2 = _config.getConfig('ortb2') || {}; - const dataSite = _config.getConfig('ortb2.site.ext.data') || {}; - const dataUser = _config.getConfig('ortb2.user.ext.data') || {}; + const ortb2 = reqBidsConfigObj.ortb2Fragments?.global || {}; + const dataSite = deepAccess(ortb2, 'site.ext.data') || {}; + const dataUser = deepAccess(ortb2, 'user.ext.data') || {}; _each(response, (v0, k0) => { if (k0 == '_') return; @@ -243,7 +242,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { deepSetValue(ortb2, 'site.ext.data', dataSite); deepSetValue(ortb2, 'user.ext.data', dataUser); - _config.setConfig({ ortb2 }); + deepSetValue(reqBidsConfigObj, 'ortb2Fragments.global', ortb2); adUnits.forEach((adUnit, i) => { _each(response['_'][i], (v0, k0) => { @@ -305,7 +304,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { [ 'imp', config.params.imps ], [ 'fc_ip', config.params.freqcap_ip ], [ 'fc_ipua', config.params.freqcap_ipua ], - [ 'pn', (refererInfo.canonicalUrl || refererInfo.referer || '').substr(0, 300).split(/[?#]/)[0] ] + [ 'pn', (refererInfo.page || '').substr(0, 300).split(/[?#]/)[0] ] ]; if (!adUnits.length) { @@ -363,7 +362,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { }); } -function getTargetingData(adUnitArray, config, userConsent) { +function getTargetingData(adUnitArray, config, userConsent, auction) { function targetingNormalise(v) { if (isArray(v) && v.length == 0) return undefined; if (isBoolean(v)) v = ~~v; @@ -371,10 +370,11 @@ function getTargetingData(adUnitArray, config, userConsent) { return v; } - const dataSite = _config.getConfig(`ortb2.site.ext.data.${MODULE_NAME}_rtd`) || {}; + const ortb2 = auction.getFPD().global || {}; + const dataSite = deepAccess(ortb2, `site.ext.data.${MODULE_NAME}_rtd`) || {}; if (!dataSite.ok) return {}; - const dataUser = _config.getConfig(`ortb2.user.ext.data.${MODULE_NAME}_rtd`) || {}; + const dataUser = deepAccess(ortb2, `user.ext.data.${MODULE_NAME}_rtd`) || {}; return getGlobal().adUnits.filter(adUnit => includes(adUnitArray, adUnit.code)).reduce((a, adUnit) => { a[adUnit.code] = {}; diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index 21bcb6cee26..ed984645d0d 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -2,6 +2,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isFn, deepAccess, logMessage } from '../src/utils.js'; import {config} from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'adman'; const AD_URL = 'https://pub.admanmedia.com/?c=o&m=multi'; @@ -63,10 +64,14 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let winTop = window; let location; + // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin try { - location = new URL(bidderRequest.refererInfo.referer) + location = new URL(bidderRequest.refererInfo.page) winTop = window.top; } catch (e) { location = winTop.location; diff --git a/modules/admaticBidAdapter.md b/modules/admaticBidAdapter.md deleted file mode 100644 index f6e822b9c06..00000000000 --- a/modules/admaticBidAdapter.md +++ /dev/null @@ -1,54 +0,0 @@ -# Overview - -``` -Module Name: AdMatic Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid@admatic.com.tr -``` - -# Description - -Module that connects to AdMatic demand sources - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300, 250]], // a display size - } - }, - bids: [ - { - bidder: "admatic", - params: { - pid: 193937152158, // publisher id without "adm-pub-" prefix - wid: 104276324971, // website id - priceType: 'gross', // default is net - url: window.location.href || window.top.location.href //page url from js - } - } - ] - },{ - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[320, 50]], // a mobile size - } - }, - bids: [ - { - bidder: "admatic", - params: { - pid: 193937152158, // publisher id without "adm-pub-" prefix - wid: 104276324971, // website id - priceType: 'gross', // default is net - url: window.location.href || window.top.location.href //page url from js - } - } - ] - } - ]; -``` diff --git a/modules/admediaBidAdapter.md b/modules/admediaBidAdapter.md deleted file mode 100644 index a03a7b49529..00000000000 --- a/modules/admediaBidAdapter.md +++ /dev/null @@ -1,42 +0,0 @@ -# Overview - -``` -Module Name: Admedia Bidder Adapter -Module Type: Bidder Adapter -Maintainer: developers@admedia.com -``` - -# Description - -Admedia Bidder Adapter for Prebid.js. -Only Banner format is supported. - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div-0', - sizes: [[300, 250]], // a display size - bids: [ - { - bidder: 'admedia', - params: { - aid: 86858 - } - } - ] - }, - { - code: 'test-div-1', - sizes: [[300, 50]], // a mobile size - bids: [ - { - bidder: 'admedia', - params: { - aid: 86858 - } - } - ] - } - ]; -``` diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index dfb76a03804..8fcee60f9f9 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -2,6 +2,7 @@ import { logError } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'admixer'; const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads']; @@ -20,6 +21,9 @@ export const spec = { * Make a server request from the list of BidRequests. */ buildRequests: function (validRequest, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validRequest = convertOrtbRequestToProprietaryNative(validRequest); + let w; let docRef; do { @@ -32,22 +36,23 @@ export const spec = { } while (w !== window.top); const payload = { imps: [], - ortb2: config.getConfig('ortb2'), + ortb2: bidderRequest.ortb2, docReferrer: docRef, }; let endpointUrl; if (bidderRequest) { const {bidderCode} = bidderRequest; endpointUrl = config.getConfig(`${bidderCode}.endpoint_url`); - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + // TODO: is 'page' the right value here? + if (bidderRequest.refererInfo?.page) { + payload.referrer = encodeURIComponent(bidderRequest.refererInfo.page); } if (bidderRequest.gdprConsent) { payload.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, // will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true gdprApplies: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true - } + }; } if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; diff --git a/modules/adnowBidAdapter.js b/modules/adnowBidAdapter.js index 472d0fdb2e1..f83dbf68a1f 100644 --- a/modules/adnowBidAdapter.js +++ b/modules/adnowBidAdapter.js @@ -2,6 +2,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {deepAccess, parseQueryStringParameters, parseSizesInput} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'adnow'; const ENDPOINT = 'https://n.ads3-adnow.com/a'; @@ -48,6 +49,9 @@ export const spec = { * @return {ServerRequest} */ buildRequests(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + return validBidRequests.map(req => { const mediaType = this._isBannerRequest(req) ? BANNER : NATIVE; const codeId = parseInt(req.params.codeId, 10); @@ -63,7 +67,7 @@ export const spec = { if (mediaType === BANNER) { data.sizes = parseSizesInput( req.mediaTypes && req.mediaTypes.banner && req.mediaTypes.banner.sizes - ).join('|') + ).join('|'); } else { data.width = data.height = 200; diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 9e05ea664d8..0ae8411c073 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,12 +1,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { isStr, deepAccess } from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { isStr, deepAccess, logInfo } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'adnuntius'; const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; const GVLID = 855; +const DEFAULT_VAST_VERSION = 'vast4' const checkSegment = function (segment) { if (isStr(segment)) return segment; @@ -27,7 +28,7 @@ const getSegmentsFromOrtb = function (ortb2) { } const handleMeta = function () { - const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}) + const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }) let adnMeta = null if (storage.localStorageIsEnabled()) { adnMeta = JSON.parse(storage.getDataFromLocalStorage('adn.metaData')) @@ -45,7 +46,7 @@ const getUsi = function (meta, ortb2, bidderRequest) { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function (bid) { return !!(bid.bidId || (bid.params.member && bid.params.invCode)); }, @@ -55,7 +56,7 @@ export const spec = { const bidRequests = {}; const requests = []; const request = []; - const ortb2 = config.getConfig('ortb2'); + const ortb2 = bidderRequest.ortb2 || {}; const bidderConfig = config.getConfig(); const adnMeta = handleMeta() @@ -67,31 +68,40 @@ export const spec = { request.push('tzo=' + tzo) request.push('format=json') + if (gdprApplies !== undefined) request.push('consentString=' + consentString); if (segments.length > 0) request.push('segments=' + segments.join(',')); if (usi) request.push('userId=' + usi); if (bidderConfig.useCookie === false) request.push('noCookies=true') for (var i = 0; i < validBidRequests.length; i++) { const bid = validBidRequests[i] - const network = bid.params.network || 'network'; + let network = bid.params.network || 'network'; const targeting = bid.params.targeting || {}; + if (bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context !== 'outstream') { + network += '_video' + } + bidRequests[network] = bidRequests[network] || []; bidRequests[network].push(bid); networks[network] = networks[network] || {}; networks[network].adUnits = networks[network].adUnits || []; - if (bidderRequest && bidderRequest.refererInfo) networks[network].context = bidderRequest.refererInfo.referer; + if (bidderRequest && bidderRequest.refererInfo) networks[network].context = bidderRequest.refererInfo.page; if (adnMeta) networks[network].metaData = adnMeta; - networks[network].adUnits.push({ ...targeting, auId: bid.params.auId, targetId: bid.bidId }); + const adUnit = { ...targeting, auId: bid.params.auId, targetId: bid.bidId } + if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) adUnit.dimensions = bid.mediaTypes.banner.sizes + networks[network].adUnits.push(adUnit); } const networkKeys = Object.keys(networks) for (var j = 0; j < networkKeys.length; j++) { const network = networkKeys[j]; + const networkRequest = [...request] + if (network.indexOf('_video') > -1) { networkRequest.push('tt=' + DEFAULT_VAST_VERSION) } requests.push({ method: 'POST', - url: ENDPOINT_URL + '?' + request.join('&'), + url: ENDPOINT_URL + '?' + networkRequest.join('&'), data: JSON.stringify(networks[network]), bid: bidRequests[network] }); @@ -106,7 +116,7 @@ export const spec = { if (adUnit.matchedAdCount >= 1) { const ad = adUnit.ads[0]; const effectiveCpm = (ad.bid) ? ad.bid.amount * 1000 : 0; - return { + const adResponse = { ...response, [adUnit.targetId]: { requestId: adUnit.targetId, @@ -122,9 +132,19 @@ export const spec = { }, netRevenue: false, ttl: 360, - ad: adUnit.html } } + + if (adUnit.vastXml) { + adResponse[adUnit.targetId].vastXml = adUnit.vastXml + adResponse[adUnit.targetId].mediaType = 'video' + } else { + adResponse[adUnit.targetId].ad = adUnit.html + } + + logInfo('BID', adResponse) + + return adResponse } else return response }, {}); diff --git a/modules/adoceanBidAdapter.js b/modules/adoceanBidAdapter.js index bf5596ab22a..d74a78270b2 100644 --- a/modules/adoceanBidAdapter.js +++ b/modules/adoceanBidAdapter.js @@ -2,11 +2,15 @@ import { _each, parseSizesInput, isStr, isArray } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'adocean'; +const URL_SAFE_FIELDS = { + schain: true, + slaves: true +}; function buildEndpointUrl(emiter, payloadMap) { const payload = []; _each(payloadMap, function(v, k) { - payload.push(k + '=' + (k === 'schain' ? v : encodeURIComponent(v))); + payload.push(k + '=' + (URL_SAFE_FIELDS[k] ? v : encodeURIComponent(v))); }); const randomizedPart = Math.random().toString().slice(2); @@ -17,7 +21,8 @@ function buildRequest(masterBidRequests, masterId, gdprConsent) { let emiter; const payload = { id: masterId, - aosspsizes: [] + aosspsizes: [], + slaves: [] }; if (gdprConsent) { payload.gdpr_consent = gdprConsent.consentString || undefined; @@ -29,7 +34,7 @@ function buildRequest(masterBidRequests, masterId, gdprConsent) { } const bidIdMap = {}; - + const uniquePartLength = 10; _each(masterBidRequests, function(bid, slaveId) { if (!emiter) { emiter = bid.params.emiter; @@ -38,11 +43,13 @@ function buildRequest(masterBidRequests, masterId, gdprConsent) { const slaveSizes = parseSizesInput(bid.mediaTypes.banner.sizes).join('_'); const rawSlaveId = bid.params.slaveId.replace('adocean', ''); payload.aosspsizes.push(rawSlaveId + '~' + slaveSizes); + payload.slaves.push(rawSlaveId.slice(-uniquePartLength)); bidIdMap[slaveId] = bid.bidId; }); payload.aosspsizes = payload.aosspsizes.join('-'); + payload.slaves = payload.slaves.join(','); return { method: 'GET', diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js index 40809c59093..27a6821d9f5 100644 --- a/modules/adomikAnalyticsAdapter.js +++ b/modules/adomikAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {logInfo} from '../src/utils.js'; diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index ac49f7ae32d..c34af4d3d17 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -4,14 +4,14 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {isArray, isBoolean, isFn, isPlainObject, isStr, logError, replaceAuctionPrice} from '../src/utils.js'; import {find} from '../src/polyfill.js'; import {config} from '../src/config.js'; -import { OUTSTREAM } from '../src/video.js'; +import {OUTSTREAM} from '../src/video.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'adot'; const ADAPTER_VERSION = 'v2.0.0'; const BID_METHOD = 'POST'; const BIDDER_URL = 'https://dsp.adotmob.com/headerbidding{PUBLISHER_PATH}/bidrequest'; const REQUIRED_VIDEO_PARAMS = ['mimes', 'protocols']; -const DOMAIN_REGEX = new RegExp('//([^/]*)'); const FIRST_PRICE = 1; const IMP_BUILDER = { banner: buildBanner, video: buildVideo, native: buildNative }; const NATIVE_PLACEMENTS = { @@ -43,19 +43,6 @@ function tryParse(data) { } } -/** - * Extract domain from given url - * - * @param {string} url - * @returns {string|null} Extracted domain - */ -function extractDomainFromURL(url) { - if (!url || !isStr(url)) return null; - const domain = url.match(DOMAIN_REGEX); - if (isArray(domain) && domain.length === 2) return domain[1]; - return null; -} - /** * Create and return site OpenRtb object from given bidderRequest * @@ -63,19 +50,22 @@ function extractDomainFromURL(url) { * @returns {Site|null} Formatted Site OpenRtb object or null */ function getOpenRTBSiteObject(bidderRequest) { - if (!bidderRequest || !bidderRequest.refererInfo) return null; + const refererInfo = (bidderRequest && bidderRequest.refererInfo) || null; - const domain = extractDomainFromURL(bidderRequest.refererInfo.referer); + const domain = refererInfo ? refererInfo.domain : window.location.hostname; const publisherId = config.getConfig('adot.publisherId'); if (!domain) return null; return { - page: bidderRequest.refererInfo.referer, + page: refererInfo ? refererInfo.page : window.location.href, domain: domain, name: domain, publisher: { id: publisherId + }, + ext: { + schain: bidderRequest.schain } }; } @@ -97,7 +87,13 @@ function getOpenRTBDeviceObject() { */ function getOpenRTBUserObject(bidderRequest) { if (!bidderRequest || !bidderRequest.gdprConsent || !isStr(bidderRequest.gdprConsent.consentString)) return null; - return { ext: { consent: bidderRequest.gdprConsent.consentString } }; + + return { + ext: { + consent: bidderRequest.gdprConsent.consentString, + pubProvidedId: bidderRequest.userId && bidderRequest.userId.pubProvidedId, + }, + }; } /** @@ -378,6 +374,8 @@ function splitAdUnits(validBidRequests) { * @returns {Array} */ function buildRequests(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const adUnits = splitAdUnits(validBidRequests); const publisherPathConfig = config.getConfig('adot.publisherPath'); const publisherPath = publisherPathConfig === undefined ? '' : '/' + publisherPathConfig; diff --git a/modules/adpartnerBidAdapter.js b/modules/adpartnerBidAdapter.js index e8d8a43aa1b..471a0bba64a 100644 --- a/modules/adpartnerBidAdapter.js +++ b/modules/adpartnerBidAdapter.js @@ -15,12 +15,8 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { - let referer = window.location.href; - try { - referer = typeof bidderRequest.refererInfo === 'undefined' - ? window.top.location.href - : bidderRequest.refererInfo.referer; - } catch (e) {} + // TODO does it make sense to fall back to window.location.href? + const referer = bidderRequest?.refererInfo?.page || window.location.href; let bidRequests = []; let beaconParams = { diff --git a/modules/adplusBidAdapter.js b/modules/adplusBidAdapter.js index 4707ca2ff5a..6fbe1fe1dde 100644 --- a/modules/adplusBidAdapter.js +++ b/modules/adplusBidAdapter.js @@ -127,6 +127,7 @@ function createBidRequest(bid) { screenWidth: screen.width, screenHeight: screen.height, language: window.navigator.language || 'en-US', + // TODO: these should probably look at refererInfo pageUrl: window.location.href, domain: window.location.hostname, referrer: window.location.referrer, diff --git a/modules/adpod.js b/modules/adpod.js index b7c459fd66f..f1ab4bd2ef1 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -243,7 +243,7 @@ export function callPrebidCacheHook(fn, auctionInstance, bidResponse, afterBidAd let brandCategoryExclusion = config.getConfig('adpod.brandCategoryExclusion'); let adServerCatId = deepAccess(bidResponse, 'meta.adServerCatId'); if (!adServerCatId && brandCategoryExclusion) { - logWarn('Detected a bid without meta.adServerCatId while setConfig({adpod.brandCategoryExclusion}) was enabled. This bid has been rejected:', bidResponse) + logWarn('Detected a bid without meta.adServerCatId while setConfig({adpod.brandCategoryExclusion}) was enabled. This bid has been rejected:', bidResponse); afterBidAdded(); } else { if (config.getConfig('adpod.deferCaching') === false) { diff --git a/modules/adprimeBidAdapter.js b/modules/adprimeBidAdapter.js index d64874c393e..49c18c2d067 100644 --- a/modules/adprimeBidAdapter.js +++ b/modules/adprimeBidAdapter.js @@ -2,6 +2,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'adprime'; const AD_URL = 'https://delta.adprime.com/pbjs'; @@ -50,10 +51,14 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let winTop = window; let location; + // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin try { - location = new URL(bidderRequest.refererInfo.referer) + location = new URL(bidderRequest.refererInfo.page) winTop = window.top; } catch (e) { location = winTop.location; diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index 348bdc90808..048e43641f0 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -58,6 +58,7 @@ export const spec = { logInfo(request); logInfo(response); + let qid = null; const res = response && response.body && response.body.data; let bidResponses = []; @@ -86,6 +87,17 @@ export const spec = { bidResponses.push(bidResponse); logInfo('bidResponses', bidResponses); + if (res && res.qid) { + if (storage.getDataFromLocalStorage('qid')) { + qid = storage.getDataFromLocalStorage('qid'); + if (qid && qid.includes('%7B%22')) { + storage.setDataInLocalStorage('qid', res.qid); + } + } else { + storage.setDataInLocalStorage('qid', res.qid); + } + } + return bidResponses; }, @@ -178,21 +190,13 @@ export const spec = { }; function buildRequest(validBidRequests, bidderRequest) { - let qid = Math.random().toString(36).substring(2) + Date.now().toString(36); let bid = validBidRequests; - - if (storage.getDataFromLocalStorage('qid')) { - qid = storage.getDataFromLocalStorage('qid'); - } else { - storage.setDataInLocalStorage('qid', qid); - } - return { placementCode: bid.params.placementId, auctionId: bid.auctionId, - qid: qid, type: bid.params.type, adUnitCode: bid.adUnitCode, + bidQid: storage.getDataFromLocalStorage('qid') || null, bidId: bid.bidId, bidder: bid.bidder, bidderRequestId: bid.bidderRequestId, diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 3d4de7c7b9d..40cfe18f025 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -21,6 +21,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'adrelevantis'; const URL = 'https://ssp.adrelevantis.com/prebid'; @@ -71,6 +72,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + const tags = bidRequests.map(bidToTag); const userObjBid = find(bidRequests, hasUserInfo); let userObj; @@ -127,7 +131,8 @@ export const spec = { if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { - rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + // TODO: this sends everything it finds to the backend, except for canonicalUrl + rd_ref: encodeURIComponent(bidderRequest.refererInfo.topmostLocation), rd_top: bidderRequest.refererInfo.reachedTop, rd_ifs: bidderRequest.refererInfo.numIframes, rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') @@ -135,13 +140,12 @@ export const spec = { payload.referrer_detection = refererinfo; } - let fpdcfg = config.getLegacyFpd(config.getConfig('ortb2')); - if (fpdcfg && fpdcfg.context) { - let fdata = { - keywords: fpdcfg.context.keywords || '', - category: fpdcfg.context.data.category || '' + const ortb2Site = bidderRequest.ortb2?.site; + if (ortb2Site) { + payload.fpd = { + keywords: ortb2Site.keywords || '', + category: deepAccess(ortb2Site, 'ext.data.category') || '' } - payload.fpd = fdata; } const request = formatRequest(payload, bidderRequest); @@ -205,7 +209,7 @@ export const spec = { return params; } -} +}; function isPopulatedArray(arr) { return !!(isArray(arr) && arr.length > 0); @@ -440,11 +444,18 @@ function bidToTag(bid) { tag.cpm = bid.params.cpm; } tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.use_pmt_rule = bid.params.usePaymentRule || false; tag.prebid = true; tag.disable_psa = true; if (bid.params.position) { tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; + } else { + let mediaTypePos = deepAccess(bid, `mediaTypes.banner.pos`) || deepAccess(bid, `mediaTypes.video.pos`); + // only support unknown, atf, and btf values for position at this time + if (mediaTypePos === 0 || mediaTypePos === 1 || mediaTypePos === 3) { + // ortb spec treats btf === 3, but our system interprets btf === 2; so converting the ortb value here for consistency + tag.position = (mediaTypePos === 3) ? 2 : mediaTypePos; + } } if (bid.params.trafficSourceCode) { tag.traffic_source_code = bid.params.trafficSourceCode; diff --git a/modules/adrinoBidAdapter.js b/modules/adrinoBidAdapter.js index 4520066c3e7..2a5588eeb8c 100644 --- a/modules/adrinoBidAdapter.js +++ b/modules/adrinoBidAdapter.js @@ -1,6 +1,8 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {triggerPixel} from '../src/utils.js'; import {NATIVE} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'adrino'; const REQUEST_METHOD = 'POST'; @@ -12,6 +14,10 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [NATIVE], + getBidderConfig: function (property) { + return config.getConfig(`${BIDDER_CODE}.${property}`); + }, + isBidRequestValid: function (bid) { return !!(bid.bidId) && !!(bid.params) && @@ -23,14 +29,19 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const bidRequests = []; for (let i = 0; i < validBidRequests.length; i++) { + let host = this.getBidderConfig('host') || BIDDER_HOST; + let requestData = { bidId: validBidRequests[i].bidId, nativeParams: validBidRequests[i].nativeParams, placementHash: validBidRequests[i].params.hash, - referer: bidderRequest.refererInfo.referer, + userId: validBidRequests[i].userId, + referer: bidderRequest.refererInfo.page, userAgent: navigator.userAgent, } @@ -43,7 +54,7 @@ export const spec = { bidRequests.push({ method: REQUEST_METHOD, - url: BIDDER_HOST + '/bidder/bid/', + url: host + '/bidder/bid/', data: requestData, options: { contentType: 'application/json', @@ -66,7 +77,8 @@ export const spec = { onBidWon: function (bid) { if (bid['requestId']) { - triggerPixel(BIDDER_HOST + '/bidder/won/' + bid['requestId']); + let host = this.getBidderConfig('host') || BIDDER_HOST; + triggerPixel(host + '/bidder/won/' + bid['requestId']); } } }; diff --git a/modules/adrinoBidAdapter.md b/modules/adrinoBidAdapter.md index 5ec63a72736..ab655f700fc 100644 --- a/modules/adrinoBidAdapter.md +++ b/modules/adrinoBidAdapter.md @@ -13,6 +13,12 @@ Module connects to Adrino bidder to fetch bids. Only native format is supported. # Test Parameters ``` +pbjs.setConfig({ + adrino: { + host: 'https://custom-domain.adrino.io' + } +}); + var adUnits = [ code: '/12345678/prebid_native_example_1', mediaTypes: { diff --git a/modules/adriverBidAdapter.js b/modules/adriverBidAdapter.js index 5ab417520e9..1af0cffa700 100644 --- a/modules/adriverBidAdapter.js +++ b/modules/adriverBidAdapter.js @@ -1,6 +1,6 @@ // ADRIVER BID ADAPTER for Prebid 1.13 import { logInfo, getWindowLocation, getBidIdParameter, _each } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'adriver'; @@ -22,8 +22,6 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { - logInfo('validBidRequests', validBidRequests); - let win = getWindowLocation(); let customID = Math.round(Math.random() * 999999999) + '-' + Math.round(new Date() / 1000) + '-1-46-'; let siteId = getBidIdParameter('siteid', validBidRequests[0].params) + ''; @@ -32,7 +30,7 @@ export const spec = { let timeout = null; if (bidderRequest) { - timeout = bidderRequest.timeout + timeout = bidderRequest.timeout; } const payload = { @@ -99,21 +97,16 @@ export const spec = { }); }); - let userid = validBidRequests[0].userId; - let adrcidCookie = storage.getDataFromLocalStorage('adrcid') || validBidRequests[0].userId.adrcid; - + let adrcidCookie = storage.getDataFromLocalStorage('adrcid') || validBidRequests[0].userId?.adrcid; if (adrcidCookie) { - payload.adrcid = adrcidCookie; - payload.id5 = userid.id5id; - payload.sharedid = userid.pubcid; - payload.unifiedid = userid.tdid; + payload.user.buyerid = adrcidCookie; } const payloadString = JSON.stringify(payload); return { method: 'POST', url: ADRIVER_BID_URL, - data: payloadString, + data: payloadString }; }, diff --git a/modules/adriverIdSystem.js b/modules/adriverIdSystem.js index 6a492fac508..fb8ce99ec16 100644 --- a/modules/adriverIdSystem.js +++ b/modules/adriverIdSystem.js @@ -73,7 +73,8 @@ export const adriverIdSubmodule = { callback(); } }; - ajax(url, callbacks, undefined, {method: 'GET'}); + let newUrl = url + '&cid=' + (storage.getDataFromLocalStorage('adrcid') || storage.getCookie('adrcid')); + ajax(newUrl, callbacks, undefined, {method: 'GET'}); } }; return {callback: resp}; diff --git a/modules/adspendBidAdapter.md b/modules/adspendBidAdapter.md deleted file mode 100644 index dc3409b0057..00000000000 --- a/modules/adspendBidAdapter.md +++ /dev/null @@ -1,60 +0,0 @@ -# Overview - -``` -Module Name: AdSpend Bidder Adapter -Module Type: Bidder Adapter -Maintainer: gaffoonster@gmail.com -``` - -# Description - -Connects to AdSpend bidder. -AdSpend adapter supports only Banner at the moment. Video and Native will be add soon. - -# Test Parameters -``` -var adUnits = [ - // Banner - { - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - // You can choose one of them - sizes: [ - [300, 250], - [300, 600], - [240, 400], - [728, 90], - ] - } - }, - bids: [ - { - bidder: "adspend", - params: { - bidfloor: 1, - placement: 'test', - tagId: 'test-ad', - } - } - ] - } -]; - -pbjs.que.push(() => { - pbjs.setConfig({ - userSync: { - syncEnabled: true, - enabledBidders: ['adspend'], - pixelEnabled: true, - syncsPerBidder: 200, - syncDelay: 100, - }, - currency: { - adServerCurrency: 'RUB' // We work only with rubles for now - } - }); -}); -``` - -**It's a test banner, so you'll see some errors in console cause it will be trying to call our system's events.** diff --git a/modules/adspiritBidAdapter.md b/modules/adspiritBidAdapter.md deleted file mode 100644 index 688d0814882..00000000000 --- a/modules/adspiritBidAdapter.md +++ /dev/null @@ -1,28 +0,0 @@ -# Overview - -**Module Name**: AdSpirit Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: prebid@adspirit.de - -# Description - -Module that connects to an AdSpirit zone to fetch bids. - -# Test Parameters -``` - var adUnits = [ - { - code: 'display-div', - sizes: [[300, 250]], // a display size - bids: [ - { - bidder: "adspirit", - params: { - placementId: '5', - host: 'n1test.adspirit.de' - } - } - ] - } - ]; -``` diff --git a/modules/adtargetBidAdapter.js b/modules/adtargetBidAdapter.js index a07b0de0f67..89ba4878acf 100644 --- a/modules/adtargetBidAdapter.js +++ b/modules/adtargetBidAdapter.js @@ -117,7 +117,8 @@ function parseResponse(serverResponse, adapterRequest) { function bidToTag(bidRequests, adapterRequest) { const tag = { - Domain: deepAccess(adapterRequest, 'refererInfo.referer') + // TODO: is 'page' the right value here? + Domain: deepAccess(adapterRequest, 'refererInfo.page') }; if (config.getConfig('coppa') === true) { tag.Coppa = 1; @@ -136,7 +137,7 @@ function bidToTag(bidRequests, adapterRequest) { tag.UserIds = deepAccess(bidRequests[0], 'userId'); } - const bids = [] + const bids = []; for (let i = 0, length = bidRequests.length; i < length; i++) { const bid = prepareBidRequests(bidRequests[i]); diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index 13174ff337c..a7c83d9c190 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -22,6 +22,8 @@ const HOST_GETTERS = { streamkey: () => 'ghb.hb.streamkey.net', janet: () => 'ghb.bidder.jmgads.com', pgam: () => 'ghb.pgamssp.com', + ocm: () => 'ghb.cenarius.orangeclickmedia.com', + vidcrunchllc: () => 'ghb.platform.vidcrunch.com', } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -37,9 +39,12 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 410, - aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', 'streamkey', 'janet', + aliases: ['onefiftytwomedia', 'appaloosa', 'bidsxchange', 'streamkey', 'janet', + { code: 'selectmedia', gvlid: 775 }, { code: 'navelix', gvlid: 380 }, - 'pgam' + 'pgam', + { code: 'ocm', gvlid: 1148 }, + { code: 'vidcrunchllc', gvlid: 1145 }, ], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { @@ -160,7 +165,8 @@ function parseRTBResponse(serverResponse, adapterRequest) { function bidToTag(bidRequests, adapterRequest) { // start publisher env const tag = { - Domain: deepAccess(adapterRequest, 'refererInfo.referer') + // TODO: is 'page' the right value here? + Domain: deepAccess(adapterRequest, 'refererInfo.page') }; if (config.getConfig('coppa') === true) { tag.Coppa = 1; @@ -186,7 +192,7 @@ function bidToTag(bidRequests, adapterRequest) { } // end publisher env - const bids = [] + const bids = []; for (let i = 0, length = bidRequests.length; i < length; i++) { const bid = prepareBidRequests(bidRequests[i]); diff --git a/modules/adtrgtmeBidAdapter.js b/modules/adtrgtmeBidAdapter.js new file mode 100644 index 00000000000..4dc95ce6bc1 --- /dev/null +++ b/modules/adtrgtmeBidAdapter.js @@ -0,0 +1,333 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { deepAccess, isFn, isStr, isNumber, isArray, isEmpty, isPlainObject, generateUUID, logWarn } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { hasPurpose1Consent } from '../src/utils/gpdr.js'; + +const INTEGRATION_METHOD = 'prebid.js'; +const BIDDER_CODE = 'adtrgtme'; +const ENDPOINT = 'https://z.cdn.adtarget.market/ssp?prebid&s='; +const ADAPTER_VERSION = '1.0.0'; +const PREBID_VERSION = '$prebid.version$'; +const DEFAULT_BID_TTL = 300; +const DEFAULT_CURRENCY = 'USD'; + +function transformSizes(sizes) { + const getSize = (size) => { + return { + w: parseInt(size[0]), + h: parseInt(size[1]) + } + } + if (isArray(sizes) && sizes.length === 2 && !isArray(sizes[0])) { + return [ getSize(sizes) ]; + } + return sizes.map(getSize); +} + +function extractUserSyncUrls(syncOptions, pixels) { + let itemsRegExp = /(img|iframe)[\s\S]*?src\s*=\s*("|')(.*?)\2/gi; + let tagNameRegExp = /\w*(?=\s)/; + let srcRegExp = /src=("|')(.*?)\1/; + let userSyncObjects = []; + + if (pixels) { + let matchedItems = pixels.match(itemsRegExp); + if (matchedItems) { + matchedItems.forEach(item => { + let tagName = item.match(tagNameRegExp)[0]; + let url = item.match(srcRegExp)[2]; + if (tagName && url) { + let tagType = tagName.toLowerCase() === 'img' ? 'image' : 'iframe'; + if ((!syncOptions.iframeEnabled && tagType === 'iframe') || + (!syncOptions.pixelEnabled && tagType === 'image')) { + return; + } + userSyncObjects.push({ + type: tagType, + url: url + }); + } + }); + } + } + return userSyncObjects; +} + +function isSecure(bid) { + return deepAccess(bid, 'params.bidOverride.imp.secure') || (document.location.protocol === 'https:') ? 1 : 0; +}; + +function getMediaType(bid) { + return deepAccess(bid, 'mediaTypes.banner') ? BANNER : false; +} + +function validateAppendObject(validationFunction, allowedKeys, inputObject, appendToObject) { + const outputObject = { + ...appendToObject + }; + if (allowedKeys.length > 0 && typeof validationFunction === 'function') { + for (const objectKey in inputObject) { + if (allowedKeys.indexOf(objectKey) !== -1 && validationFunction(inputObject[objectKey])) { + outputObject[objectKey] = inputObject[objectKey] + } + } + } + return outputObject; +}; + +function getTtl(bidderRequest) { + const ttl = config.getConfig('adtrgtme.ttl'); + const validateTTL = (ttl) => { + return (isNumber(ttl) && ttl > 0 && ttl < 3600) ? ttl : DEFAULT_BID_TTL + }; + return ttl ? validateTTL(ttl) : validateTTL(deepAccess(bidderRequest, 'params.ttl')); +}; + +function getFloorModuleData(bid) { + const getFloorRequestObject = { + currency: deepAccess(bid, 'params.bidOverride.cur') || DEFAULT_CURRENCY, + mediaType: BANNER, + size: '*' + }; + return (isFn(bid.getFloor)) ? bid.getFloor(getFloorRequestObject) : false; +}; + +function generateOpenRtbObject(bidderRequest, bid) { + if (bidderRequest) { + let outBoundBidRequest = { + id: generateUUID(), + cur: [getFloorModuleData(bidderRequest).currency || deepAccess(bid, 'params.bidOverride.cur') || DEFAULT_CURRENCY], + imp: [], + site: { + page: deepAccess(bidderRequest, 'refererInfo.page') + }, + device: { + dnt: 0, + ua: navigator.userAgent, + ip: deepAccess(bid, 'params.bidOverride.device.ip') || deepAccess(bid, 'params.ext.ip') || undefined + }, + regs: { + ext: { + 'us_privacy': bidderRequest.uspConsent ? bidderRequest.uspConsent : '', + gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + } + }, + source: { + ext: { + hb: 1, + adapterver: ADAPTER_VERSION, + prebidver: PREBID_VERSION, + integration: { + name: INTEGRATION_METHOD, + ver: PREBID_VERSION + } + }, + fd: 1 + }, + user: { + ext: { + consent: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies + ? bidderRequest.gdprConsent.consentString : '' + } + } + }; + + outBoundBidRequest.site.id = bid.params.sid; + + if (bidderRequest.ortb2) { + outBoundBidRequest = appendFirstPartyData(outBoundBidRequest, bid); + }; + + if (deepAccess(bid, 'schain')) { + outBoundBidRequest.source.ext.schain = bid.schain; + outBoundBidRequest.source.ext.schain.nodes[0].rid = outBoundBidRequest.id; + }; + + return outBoundBidRequest; + }; +}; + +function appendImpObject(bid, openRtbObject) { + const mediaTypeMode = getMediaType(bid); + + if (openRtbObject && bid) { + const impObject = { + id: bid.bidId, + secure: isSecure(bid), + bidfloor: getFloorModuleData(bid).floor || deepAccess(bid, 'params.bidOverride.imp.bidfloor') || 0.000001 + }; + + if (mediaTypeMode === BANNER) { + impObject.banner = { + mimes: bid.mediaTypes.banner.mimes || ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: transformSizes(bid.sizes) + }; + if (bid.mediaTypes.banner.pos) { + impObject.banner.pos = bid.mediaTypes.banner.pos; + }; + }; + + impObject.ext = { + dfp_ad_unit_code: bid.adUnitCode + }; + + if (deepAccess(bid, 'params.zid')) { + impObject.tagid = bid.params.zid; + } + + if (deepAccess(bid, 'ortb2Imp.ext.data') && isPlainObject(bid.ortb2Imp.ext.data)) { + impObject.ext.data = bid.ortb2Imp.ext.data; + }; + + if (deepAccess(bid, 'ortb2Imp.instl') && isNumber(bid.ortb2Imp.instl) && (bid.ortb2Imp.instl === 1)) { + impObject.instl = bid.ortb2Imp.instl; + }; + + openRtbObject.imp.push(impObject); + }; +}; + +function appendFirstPartyData(outBoundBidRequest, bid) { + const ortb2Object = bid.ortb2; + const siteObject = deepAccess(ortb2Object, 'site') || undefined; + const siteContentObject = deepAccess(siteObject, 'content') || undefined; + const userObject = deepAccess(ortb2Object, 'user') || undefined; + + if (siteObject && isPlainObject(siteObject)) { + const allowedSiteStringKeys = ['name', 'domain', 'page', 'ref', 'keywords']; + const allowedSiteArrayKeys = ['cat', 'sectioncat', 'pagecat']; + const allowedSiteObjectKeys = ['ext']; + outBoundBidRequest.site = validateAppendObject(isStr, allowedSiteStringKeys, siteObject, outBoundBidRequest.site); + outBoundBidRequest.site = validateAppendObject(isArray, allowedSiteArrayKeys, siteObject, outBoundBidRequest.site); + outBoundBidRequest.site = validateAppendObject(isPlainObject, allowedSiteObjectKeys, siteObject, outBoundBidRequest.site); + }; + + if (siteContentObject && isPlainObject(siteContentObject)) { + const allowedContentStringKeys = ['id', 'title', 'language']; + const allowedContentArrayKeys = ['cat']; + outBoundBidRequest.site.content = validateAppendObject(isStr, allowedContentStringKeys, siteContentObject, outBoundBidRequest.site.content); + outBoundBidRequest.site.content = validateAppendObject(isArray, allowedContentArrayKeys, siteContentObject, outBoundBidRequest.site.content); + }; + + if (userObject && isPlainObject(userObject)) { + const allowedUserStrings = ['id', 'buyeruid', 'gender', 'keywords', 'customdata']; + const allowedUserObjects = ['ext']; + outBoundBidRequest.user = validateAppendObject(isStr, allowedUserStrings, userObject, outBoundBidRequest.user); + outBoundBidRequest.user.ext = validateAppendObject(isPlainObject, allowedUserObjects, userObject, outBoundBidRequest.user.ext); + }; + + return outBoundBidRequest; +}; + +function generateServerRequest({payload, requestOptions, bidderRequest}) { + return { + url: (config.getConfig('adtrgtme.endpoint') || ENDPOINT) + (payload.site.id || ''), + method: 'POST', + data: payload, + options: requestOptions, + bidderRequest: bidderRequest + }; +}; + +export const spec = { + code: BIDDER_CODE, + aliases: [], + supportedMediaTypes: [BANNER], + + isBidRequestValid: function(bid) { + const params = bid.params; + if (isPlainObject(params) && isNumber(params.sid)) { + return true; + } else { + logWarn('Adtrgtme bidder params missing or incorrect'); + return false; + } + }, + + buildRequests: function(validBidRequests, bidderRequest) { + if (isEmpty(validBidRequests) || isEmpty(bidderRequest)) { + logWarn('Adtrgtme Adapter: buildRequests called with empty request'); + return undefined; + }; + + const requestOptions = { + contentType: 'application/json', + customHeaders: { + 'x-openrtb-version': '2.5' + } + }; + + requestOptions.withCredentials = hasPurpose1Consent(bidderRequest.gdprConsent); + + if (config.getConfig('adtrgtme.singleRequestMode') === true) { + const payload = generateOpenRtbObject(bidderRequest, validBidRequests[0]); + validBidRequests.forEach(bid => { + appendImpObject(bid, payload); + }); + + return generateServerRequest({payload, requestOptions, bidderRequest}); + } + + return validBidRequests.map(bid => { + const payloadClone = generateOpenRtbObject(bidderRequest, bid); + appendImpObject(bid, payloadClone); + + return generateServerRequest({payload: payloadClone, requestOptions, bidderRequest: bid}); + }); + }, + + interpretResponse: function(serverResponse, { data, bidderRequest }) { + const response = []; + if (!serverResponse.body || !Array.isArray(serverResponse.body.seatbid)) { + return response; + } + + let seatbids = serverResponse.body.seatbid; + seatbids.forEach(seatbid => { + let bid; + + try { + bid = seatbid.bid[0]; + } catch (e) { + return response; + } + + let cpm = bid.price; + + let bidResponse = { + adId: deepAccess(bid, 'adId') ? bid.adId : bid.impid || bid.crid, + ad: bid.adm, + adUnitCode: bidderRequest.adUnitCode, + requestId: bid.impid, + cpm: cpm, + width: bid.w, + height: bid.h, + creativeId: bid.crid || 0, + currency: bid.cur || DEFAULT_CURRENCY, + dealId: bid.dealid ? bid.dealid : null, + netRevenue: true, + ttl: getTtl(bidderRequest), + mediaType: BANNER, + meta: { + advertiserDomains: bid.adomain, + mediaType: BANNER, + } + }; + + response.push(bidResponse); + }); + + return response; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { + const bidResponse = !isEmpty(serverResponses) && serverResponses[0].body; + if (bidResponse && bidResponse.ext && bidResponse.ext.pixels) { + return extractUserSyncUrls(syncOptions, bidResponse.ext.pixels); + } + return []; + } +}; + +registerBidder(spec); diff --git a/modules/adtrgtmeBidAdapter.md b/modules/adtrgtmeBidAdapter.md new file mode 100644 index 00000000000..d136b17067d --- /dev/null +++ b/modules/adtrgtmeBidAdapter.md @@ -0,0 +1,69 @@ +# Overview + +**Module Name**: adtrgtme Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: info@adtarget.me + +# Description +The Adtrgtme Bid Adapter is an OpenRTB interface that support display demand from Adtarget + +# Supported Features: +* Media Types: Banner +* Multi-format adUnits +* Price floors module +* Advertiser domains + +# Mandatory Bidder Parameters +The minimal requirements for the 'adtrgtme' bid adapter to generate an outbound bid-request to our Adtrgtme are: +1. At least 1 banner adUnit +2. Your Adtrgtme site id **bidder.params**.**sid** + +## Example: +```javascript +const adUnits = [{ + code: 'your-placement', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'adtrgtme', + params: { + sid: 1220291391, // Site/App ID provided from SSP + } + } + ] +}]; +``` + +# Optional: Price floors module & bidfloor +The adtargerme adapter supports the Prebid.org Price Floors module and will use it to define the outbound bidfloor and currency. +By default the adapter will always check the existance of Module price floor. +If a module price floor does not exist you can set a custom bid floor for your impression using "params.bidOverride.imp.bidfloor". + +```javascript +const adUnits = [{ + code: 'your-placement', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + bids: [{ + bidder: 'adtrgtme', + params: { + sid: 1220291391, + bidOverride :{ + imp: { + bidfloor: 5.00 // bidOverride bidfloor + } + } + } + } + }] +}]; +``` \ No newline at end of file diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index 283e1273150..2ec5cd59e1d 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'adtrue'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -44,7 +45,7 @@ const VIDEO_CUSTOM_PARAMS = { 'placement': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER -} +}; const NATIVE_ASSETS = { 'TITLE': {ID: 1, KEY: 'title', TYPE: 0}, @@ -133,8 +134,9 @@ function _parseAdSlot(bid) { function _initConf(refererInfo) { return { - pageURL: (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href, - refURL: window.document.referrer + // TODO: do the fallbacks make sense here? + pageURL: refererInfo?.page || window.location.href, + refURL: refererInfo?.ref || window.document.referrer }; } @@ -449,6 +451,9 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; diff --git a/modules/aduptechBidAdapter.js b/modules/aduptechBidAdapter.js index 1186e0410ab..5d8b69af77f 100644 --- a/modules/aduptechBidAdapter.js +++ b/modules/aduptechBidAdapter.js @@ -1,7 +1,7 @@ -import { deepAccess, getWindowTop, getWindowSelf, getAdUnitSizes } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js' +import {deepAccess, getAdUnitSizes} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; export const BIDDER_CODE = 'aduptech'; export const ENDPOINT_URL_PUBLISHER_PLACEHOLDER = '{PUBLISHER}'; @@ -37,19 +37,8 @@ export const internal = { * @returns {string} */ extractPageUrl: (bidderRequest) => { - if (bidderRequest && deepAccess(bidderRequest, 'refererInfo.canonicalUrl')) { - return bidderRequest.refererInfo.canonicalUrl; - } - - if (config && config.getConfig('pageUrl')) { - return config.getConfig('pageUrl'); - } - - try { - return getWindowTop().location.href; - } catch (e) { - return getWindowSelf().location.href; - } + // TODO: does it make sense to fall back here? + return bidderRequest?.refererInfo?.page || window.location.href; }, /** @@ -59,15 +48,8 @@ export const internal = { * @returns {string} */ extractReferrer: (bidderRequest) => { - if (bidderRequest && deepAccess(bidderRequest, 'refererInfo.referer')) { - return bidderRequest.refererInfo.referer; - } - - try { - return getWindowTop().document.referrer; - } catch (e) { - return getWindowSelf().document.referrer; - } + // TODO: does it make sense to fall back here? + return bidderRequest?.refererInfo?.ref || window.document.referrer; }, /** @@ -113,6 +95,30 @@ export const internal = { return null; }, + /** + * Extracts the floor price params from given bidRequest + * + * @param {BidRequest} bidRequest + * @returns {undefined|float} + */ + extractFloorPrice: (bidRequest) => { + let floorPrice; + if (bidRequest && bidRequest.params && bidRequest.params.floor) { + // if there is a manual floorPrice set + floorPrice = !isNaN(parseInt(bidRequest.params.floor)) ? bidRequest.params.floor : undefined; + } + if (typeof bidRequest.getFloor === 'function') { + // use prebid floor module + let floorInfo; + try { + floorInfo = bidRequest.getFloor(); + } catch (e) {} + floorPrice = typeof floorInfo === 'object' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : floorPrice; + } + + return floorPrice; + }, + /** * Group given array of bidRequests by params.publisher * @@ -193,6 +199,9 @@ export const spec = { * @returns {Object[]} */ buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const requests = []; // stop here on invalid or empty data @@ -248,6 +257,12 @@ export const spec = { bid.native = nativeConfig; } + // add floor price + const floorPrice = internal.extractFloorPrice(bidRequest); + if (floorPrice) { + bid.floorPrice = floorPrice; + } + request.data.imp.push(bid); }); diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js index 605e19cfc66..4963150caed 100755 --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -1,5 +1,4 @@ import {deepAccess, generateUUID, isEmpty, isFn, parseSizesInput, parseUrl} from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; @@ -200,12 +199,8 @@ function getBannerSizes(bid) { return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); } -function getTopWindowReferrer() { - try { - return window.top.document.referrer; - } catch (e) { - return ''; - } +function getTopWindowReferrer(bidderRequest) { + return bidderRequest?.refererInfo?.ref || ''; } function getVideoTargetingParams(bid) { @@ -226,7 +221,7 @@ function getVideoTargetingParams(bid) { function createVideoRequestData(bid, bidderRequest) { let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(); + let topReferrer = getTopWindowReferrer(bidderRequest); let sizes = getVideoSizes(bid); let firstSize = getFirstSize(sizes); @@ -309,13 +304,12 @@ function createVideoRequestData(bid, bidderRequest) { } function getTopWindowLocation(bidderRequest) { - let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; - return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); + return parseUrl(bidderRequest?.refererInfo?.page, {decodeSearchAsString: true}); } function createBannerRequestData(bid, bidderRequest) { let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(); + let topReferrer = getTopWindowReferrer(bidderRequest); let sizes = getBannerSizes(bid); let bidfloor = (getBannerBidFloor(bid) == null || typeof getBannerBidFloor(bid) == 'undefined') ? 2 : getBannerBidFloor(bid); diff --git a/modules/advenueBidAdapter.md b/modules/advenueBidAdapter.md deleted file mode 100644 index ec5287330db..00000000000 --- a/modules/advenueBidAdapter.md +++ /dev/null @@ -1,27 +0,0 @@ -# Overview - -``` -Module Name: Advenue SSP Bidder Adapter -Module Type: Bidder Adapter -Maintainer: dev.advenue@gmail.com -``` - -# Description - -Module that connects to Advenue SSP demand sources - -# Test Parameters -``` - var adUnits = [{ - code: 'placementCode', - sizes: [[300, 250]], - bids: [{ - bidder: 'advenue', - params: { - placementId: 0, - traffic: 'banner' - } - }] - } - ]; -``` diff --git a/modules/advertlyBidAdapter.md b/modules/advertlyBidAdapter.md deleted file mode 100755 index b6cc3bfe71d..00000000000 --- a/modules/advertlyBidAdapter.md +++ /dev/null @@ -1,50 +0,0 @@ -# Overview - -``` -Module Name: Advertly Bid Adapter -Module Type: Bidder Adapter -Maintainer : support@advertly.com -``` - -# Description - -Connects to Advertly Ad Server for bids. - -advertly bid adapter supports Banner and Video. - -# Test Parameters -``` - var adUnits = [ - //bannner object - { - code: 'banner-ad-slot', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [{ - bidder: 'advertly', - params: { - publisherId: 2 - } - }] - - }, - //video object - { - code: 'video-ad-slot', - mediaTypes: { - video: { - context: 'instream', - playerSize: [640, 480], - }, - }, - bids: [{ - bidder: "advertly", - params: { - publisherId: 2 - } - }] - }]; -``` diff --git a/modules/adxcgAnalyticsAdapter.js b/modules/adxcgAnalyticsAdapter.js index 5cd04ce13cd..f3bb1270334 100644 --- a/modules/adxcgAnalyticsAdapter.js +++ b/modules/adxcgAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { parseSizesInput, uniques, buildUrl, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 81872100cd1..4d6fd6fe841 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -2,21 +2,22 @@ 'use strict'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {NATIVE, BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import { - mergeDeep, _map, deepAccess, - getDNT, - parseSizesInput, deepSetValue, - isStr, + getDNT, isArray, isPlainObject, - parseUrl, - replaceAuctionPrice, triggerPixel + isStr, + mergeDeep, + parseSizesInput, + replaceAuctionPrice, + triggerPixel } from '../src/utils.js'; import {config} from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const { getConfig } = config; @@ -65,9 +66,12 @@ export const spec = { return !!(adzoneid); }, buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let app, site; - const commonFpd = getConfig('ortb2') || {}; + const commonFpd = bidderRequest.ortb2 || {}; let { user } = commonFpd; if (typeof getConfig('app') === 'object') { @@ -82,8 +86,8 @@ export const spec = { } if (!site.page) { - site.page = bidderRequest.refererInfo.referer; - site.domain = parseUrl(bidderRequest.refererInfo.referer).hostname; + site.page = bidderRequest.refererInfo.page; + site.domain = bidderRequest.refererInfo.domain; } } diff --git a/modules/adxpremiumAnalyticsAdapter.js b/modules/adxpremiumAnalyticsAdapter.js index 9066c26fb00..9161c6338f4 100644 --- a/modules/adxpremiumAnalyticsAdapter.js +++ b/modules/adxpremiumAnalyticsAdapter.js @@ -1,6 +1,6 @@ import {deepClone, logError, logInfo} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import {includes} from '../src/polyfill.js'; @@ -95,7 +95,8 @@ function auctionInit(args) { completeObject.auction_id = args.auctionId; completeObject.publisher_id = adxpremiumAnalyticsAdapter.initOptions.pubId; - try { completeObject.referer = encodeURI(args.bidderRequests[0].refererInfo.referer.split('?')[0]); } catch (e) { logError('AdxPremium Analytics - ' + e.message); } + // TODO: is 'page' the right value here? + try { completeObject.referer = encodeURI(args.bidderRequests[0].refererInfo.page.split('?')[0]); } catch (e) { logError('AdxPremium Analytics - ' + e.message); } if (args.adUnitCodes && args.adUnitCodes.length > 0) { elementIds = args.adUnitCodes; } @@ -232,6 +233,7 @@ function sendEventFallback() { } function sendEvent(completeObject) { + if (!adxpremiumAnalyticsAdapter.enabled) return; requestDelivered = true; try { let responseEvents = btoa(JSON.stringify(completeObject)); @@ -261,7 +263,7 @@ adxpremiumAnalyticsAdapter.enableAnalytics = function (config) { } adxpremiumAnalyticsAdapter.originEnableAnalytics(config); // call the base class function -} +}; adapterManager.registerAnalyticsAdapter({ adapter: adxpremiumAnalyticsAdapter, diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 784afd6cfe1..b0b132abd1c 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -1,9 +1,9 @@ import {buildUrl, deepAccess, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; import {createEidsArray} from './userId/eids.js'; import {find} from '../src/polyfill.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const VERSION = '1.0'; const BIDDER_CODE = 'adyoulike'; @@ -61,6 +61,8 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); let hasVideo = false; const payload = { Version: VERSION, @@ -116,6 +118,10 @@ export const spec = { payload.uspConsent = bidderRequest.uspConsent; } + if (bidderRequest.ortb2) { + payload.ortb2 = bidderRequest.ortb2; + } + if (deepAccess(bidderRequest, 'userId')) { payload.userId = createEidsArray(bidderRequest.userId); } @@ -168,23 +174,6 @@ function getHostname(bidderRequest) { return ''; } -/* Get current page canonical url */ -function getCanonicalUrl() { - let link; - if (window.self !== window.top) { - try { - link = window.top.document.head.querySelector('link[rel="canonical"][href]'); - } catch (e) { } - } else { - link = document.head.querySelector('link[rel="canonical"][href]'); - } - - if (link) { - return link.href; - } - return ''; -} - /* Get mediatype from bidRequest */ function getMediatype(bidRequest) { if (deepAccess(bidRequest, 'mediaTypes.banner')) { @@ -236,27 +225,28 @@ function createEndpoint(bidRequests, bidderRequest, hasVideo) { /* Create endpoint query string */ function createEndpointQS(bidderRequest) { const qs = {}; - if (bidderRequest) { const ref = bidderRequest.refererInfo; - if (ref) { - qs.RefererUrl = encodeURIComponent(ref.referer); + if (ref?.location) { + // RefererUrl will be removed in a future version. + qs.RefererUrl = encodeURIComponent(ref.location); if (ref.numIframes > 0) { qs.SafeFrame = true; } } + + const siteInfo = bidderRequest.ortb2?.site; + if (siteInfo) { + qs.PageUrl = encodeURIComponent(siteInfo.page); + qs.PageReferrer = encodeURIComponent(siteInfo.ref || ref?.location); + } } - const can = getCanonicalUrl(); + const can = bidderRequest?.refererInfo?.canonicalUrl; if (can) { qs.CanonicalUrl = encodeURIComponent(can); } - const domain = config.getConfig('publisherDomain'); - if (domain) { - qs.PublisherDomain = encodeURIComponent(domain); - } - return qs; } @@ -454,7 +444,7 @@ function getNativeAssets(response, nativeConfig) { /* Create bid from response */ function createBid(response, bidRequests) { if (!response || (!response.Ad && !response.Native && !response.Vast)) { - return + return; } const request = bidRequests && bidRequests[response.BidID]; diff --git a/modules/afpBidAdapter.js b/modules/afpBidAdapter.js index 6565942bcc8..f690b70973d 100644 --- a/modules/afpBidAdapter.js +++ b/modules/afpBidAdapter.js @@ -7,6 +7,7 @@ export const IS_DEV = location.hostname === 'localhost' export const BIDDER_CODE = 'afp' export const SSP_ENDPOINT = 'https://ssp.afp.ai/api/prebid' export const REQUEST_METHOD = 'POST' +// TODO: test code should be kept in tests export const TEST_PAGE_URL = 'https://rtbinsight.ru/smiert-bolshikh-dannykh-kto-na-novienkogo/' const SDK_PATH = 'https://cdn.afp.ai/ssp/sdk.js?auto_initialization=false&deploy_to_parent_window=true' const TTL = 60 @@ -96,7 +97,7 @@ export const spec = { }, buildRequests(validBidRequests, {refererInfo, gdprConsent}) { const payload = { - pageUrl: IS_DEV ? TEST_PAGE_URL : refererInfo.referer, + pageUrl: IS_DEV ? TEST_PAGE_URL : refererInfo.page, gdprConsent: gdprConsent, bidRequests: validBidRequests.map(validBidRequest => { const {bidId, transactionId, sizes, params: { diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index e9011343a74..3578cc4b87e 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -9,7 +9,6 @@ import { config } from '../src/config.js'; import { submodule } from '../src/hook.js'; import { mergeDeep, - isPlainObject, deepSetValue, deepAccess, } from '../src/utils.js'; @@ -84,25 +83,15 @@ function setAudiencesToAppNexusAdUnits(adUnits, audiences) { * Pass audience data to configured bidders, using ORTB2 * @param {Object} rtdConfig * @param {Array} audiences - * @return {void} + * @return {{}} a map from bidder code to ORTB2 config */ -export function setAudiencesUsingBidderOrtb2(rtdConfig, audiences) { +export function getAudiencesAsBidderOrtb2(rtdConfig, audiences) { const bidders = deepAccess(rtdConfig, 'params.bidders'); - if (!bidders || bidders.length === 0) return; - const allBiddersConfig = config.getBidderConfig(); - const agOrtb2 = {}; + if (!bidders || bidders.length === 0) return {}; + const agOrtb2 = {} deepSetValue(agOrtb2, 'ortb2.user.ext.data.airgrid', audiences || []); - bidders.forEach((bidder) => { - let bidderConfig = {}; - if (isPlainObject(allBiddersConfig[bidder])) { - bidderConfig = allBiddersConfig[bidder]; - } - config.setBidderConfig({ - bidders: [bidder], - config: mergeDeep(bidderConfig, agOrtb2), - }); - }); + return Object.fromEntries(bidders.map(bidder => [bidder, agOrtb2])); } export function setAudiencesUsingAppNexusAuctionKeywords(audiences) { @@ -142,7 +131,7 @@ export function passAudiencesToBidders( const audiences = getMatchedAudiencesFromStorage(); if (audiences.length > 0) { setAudiencesUsingAppNexusAuctionKeywords(audiences); - setAudiencesUsingBidderOrtb2(rtdConfig, audiences); + mergeDeep(bidConfig?.ortb2Fragments?.bidder, getAudiencesAsBidderOrtb2(rtdConfig, audiences)); if (adUnits) { setAudiencesToAppNexusAdUnits(adUnits, audiences); } diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index a9364a7a05f..121dc2ef96f 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -2,6 +2,7 @@ import { getBidIdParameter, tryAppendQueryString, createTrackPixelHtml, logError import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'aja'; const URL = 'https://ad.as.amanad.adtdp.com/v2/prebid'; @@ -35,8 +36,11 @@ export const spec = { * @returns {ServerRequest|ServerRequest[]} */ buildRequests: function(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const bidRequests = []; - const pageUrl = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || undefined; + const pageUrl = bidderRequest?.refererInfo?.page || undefined; for (let i = 0, len = validBidRequests.length; i < len; i++) { const bidRequest = validBidRequests[i]; diff --git a/modules/akamaiDAPIdSystem.js b/modules/akamaiDAPIdSystem.js deleted file mode 100644 index 5e3a607d5fd..00000000000 --- a/modules/akamaiDAPIdSystem.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * This module adds DAP to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/akamaiDAPIdSubmodule - * @requires module:modules/userId - */ - -import { logMessage, logError } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { uspDataHandler } from '../src/adapterManager.js'; - -const MODULE_NAME = 'akamaiDAPId'; -const STORAGE_KEY = 'akamai_dap_token'; - -export const storage = getStorageManager(); - -/** @type {Submodule} */ -export const akamaiDAPIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - /** - * decode the stored id value for passing to bid requests - * @function - * @returns {{dapId:string}} - */ - decode(value) { - logMessage('akamaiDAPId [decode] value=', value); - return { dapId: value }; - }, - - /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {ConsentData} [consentData] - * @param {SubmoduleConfig} [config] - * @returns {IdResponse|undefined} - */ - getId(config, consentData) { - const configParams = (config && config.params); - if (!configParams) { - logError('User ID - akamaiDAPId submodule requires a valid configParams'); - return; - } else if (typeof configParams.apiHostname !== 'string') { - logError('User ID - akamaiDAPId submodule requires a valid configParams.apiHostname'); - return; - } else if (typeof configParams.domain !== 'string') { - logError('User ID - akamaiDAPId submodule requires a valid configParams.domain'); - return; - } else if (typeof configParams.type !== 'string') { - logError('User ID - akamaiDAPId submodule requires a valid configParams.type'); - return; - } - const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; - const gdprConsentString = hasGdpr ? consentData.consentString : ''; - const uspConsent = uspDataHandler.getConsentData(); - if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { - logError('User ID - akamaiDAPId submodule requires consent string to call API'); - return; - } - // XXX: retrieve first-party data here if needed - let url = ''; - let postData; - let tokenName = ''; - if (configParams.apiVersion === 'v1') { - if (configParams.type.indexOf('dap-signature:') == 0) { - let parts = configParams.type.split(':'); - let v = parts[1]; - url = `https://${configParams.apiHostname}/data-activation/v1/domain/${configParams.domain}/signature?v=${v}&gdpr=${hasGdpr}&gdpr_consent=${gdprConsentString}&us_privacy=${uspConsent}`; - tokenName = 'SigToken'; - } else { - url = `https://${configParams.apiHostname}/data-activation/v1/identity/tokenize?gdpr=${hasGdpr}&gdpr_consent=${gdprConsentString}&us_privacy=${uspConsent}`; - postData = { - 'version': 1, - 'domain': configParams.domain, - 'identity': configParams.identity, - 'type': configParams.type - }; - tokenName = 'PubToken'; - } - } else { - url = `https://${configParams.apiHostname}/data-activation/x1/domain/${configParams.domain}/identity/tokenize?gdpr=${hasGdpr}&gdpr_consent=${gdprConsentString}&us_privacy=${uspConsent}`; - postData = { - 'version': configParams.apiVersion, - 'identity': configParams.identity, - 'type': configParams.type, - 'attributes': configParams.attributes - }; - tokenName = 'x1Token'; - } - - let cb = { - success: (response, request) => { - var token = (response === '') ? request.getResponseHeader('Akamai-DAP-Token') : response; - storage.setDataInLocalStorage(STORAGE_KEY, token); - }, - error: error => { - logError('akamaiDAPId [getId:ajax.error] failed to retrieve ' + tokenName, error); - } - }; - - ajax(url, cb, JSON.stringify(postData), { contentType: 'application/json' }); - - let token = storage.getDataFromLocalStorage(STORAGE_KEY); - logMessage('akamaiDAPId [getId] returning', token); - - return { id: token }; - } -}; - -submodule('userId', akamaiDAPIdSubmodule); diff --git a/modules/akamaiDAPIdSystem.md b/modules/akamaiDAPIdSystem.md deleted file mode 100644 index 9b35709c3f2..00000000000 --- a/modules/akamaiDAPIdSystem.md +++ /dev/null @@ -1,48 +0,0 @@ -# Akamai Data Activation Platform Audience Segment ID Targeting - -The Akamai Data Activation Platform (DAP) is a privacy-first system that protects end-user privacy by only allowing them to be targeted as part of a larger cohort. DAP views hiding individuals in large cohorts as the best mechanism to prevent unauthorized tracking. - -The integration of DAP into Prebid.JS consists of creating a UserID plugin that interacts with the DAP API. The UserID module tokenizes the end-user identity into an ephemeral, secure pseudonymization called a dapId. The dapId is then supplied to the bid-stream where the SSP partner looks up cohort membership for that token, and supplies the cohorts to the rest of the bid-stream. - -In this system, no end-user identifier is supplied to the bid-stream, only cohorts. This is a foundational privacy principle DAP is built upon. - -## Onboarding - -Please reach out to your Akamai account representative(Prebid@akamai.com) to get provisioned on the DAP platform. - -## DAP Configuration - -First, make sure to add the DAP submodule to your Prebid.js package with: - -``` -gulp build --modules=akamaiDAPIdSystem,userId -``` - -The following configuration parameters are available: - -```javascript -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'akamaiDAPId', - params: { - apiHostname: '', - domain: 'your-domain.com', - type: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', - identity: ‘your@email.com’ | ‘6175551234' | ...', - apiVersion: 'v1' | 'x1', - attributes: '{ "cohorts": [ "3:14400", "5:14400", "7:0" ],"first_name": "...","last_name": "..." }' - }, - }], - auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules - } -}); -``` - -In order to make use of v1 APIs, "apiVersion" needs to explicitly mentioned as 'v1'. The "apiVersion" defaults to x1 if not specified. -"attributes" can be configured in x1 API only and not v1 APIs. Please ensure that the "attributes" value is in same format as shown above. - -Refer to the sample integration example present at below location -Prebid.js/integrationExamples/gpt/akamaidap_email_example.html -Prebid.js/integrationExamples/gpt/akamaidap_signature_example.html -Prebid.js/integrationExamples/gpt/akamaidap_x1_example.html diff --git a/modules/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index 845c0f2e574..1c2af70d737 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -6,7 +6,6 @@ * @requires module:modules/realTimeData */ import {ajax} from '../src/ajax.js'; -import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; @@ -44,17 +43,16 @@ function mergeLazy(target, source) { /** * Add real-time data & merge segments. - * @param {Object} bidConfig + * @param {Object} ortb2 destionation object to merge RTD into * @param {Object} rtd * @param {Object} rtdConfig */ -export function addRealTimeData(rtd) { +export function addRealTimeData(ortb2, rtd) { logInfo('DEBUG(addRealTimeData) - ENTER'); if (isPlainObject(rtd.ortb2)) { - let ortb2 = config.getConfig('ortb2') || {}; logMessage('DEBUG(addRealTimeData): merging original: ', ortb2); logMessage('DEBUG(addRealTimeData): merging in: ', rtd.ortb2); - config.setConfig({ortb2: mergeLazy(ortb2, rtd.ortb2)}); + mergeLazy(ortb2, rtd.ortb2); } logInfo('DEBUG(addRealTimeData) - EXIT'); } @@ -117,7 +115,7 @@ export function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) } if (jsonData) { if (jsonData.rtd) { - addRealTimeData(jsonData.rtd); + addRealTimeData(bidConfig.ortb2Fragments?.global, jsonData.rtd); onDone(); logInfo('DEBUG(generateRealTimeData) - 1'); // Don't return - ensure the data is always fresh. @@ -161,17 +159,18 @@ export const dapUtils = { }; let refreshMembership = true; let token = dapUtils.dapGetTokenFromLocalStorage(); + const ortb2 = bidConfig.ortb2Fragments.global; logMessage('token is: ', token); if (token !== null) { // If token is not null then check the membership in storage and add the RTD object if (config.segtax == 504) { // Follow the encrypted membership path - dapUtils.dapRefreshEncryptedMembership(config, token, onDone) // Get the encrypted membership from server + dapUtils.dapRefreshEncryptedMembership(ortb2, config, token, onDone) // Get the encrypted membership from server refreshMembership = false; } else { - dapUtils.dapRefreshMembership(config, token, onDone) // Get the membership from server + dapUtils.dapRefreshMembership(ortb2, config, token, onDone) // Get the membership from server refreshMembership = false; } } - dapUtils.dapRefreshToken(config, refreshMembership, onDone) // Refresh Token and membership in all the cases + dapUtils.dapRefreshToken(ortb2, config, refreshMembership, onDone) // Refresh Token and membership in all the cases } }, dapGetEntropy: function(resolve, reject) { @@ -194,7 +193,7 @@ export const dapUtils = { return token; }, - dapRefreshToken: function(config, refreshMembership, onDone) { + dapRefreshToken: function(ortb2, config, refreshMembership, onDone) { dapUtils.dapLog('Token missing or expired, fetching a new one...'); // Trigger a refresh let now = Math.round(Date.now() / 1000.0); // in seconds @@ -203,7 +202,7 @@ export const dapUtils = { dapUtils.dapTokenize(configAsync, config.identity, onDone, function(token, status, xhr, onDone) { item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; - let exp = dapUtils.dapExtractExpiryFromToken(token) + let exp = dapUtils.dapExtractExpiryFromToken(token); if (typeof exp == 'number') { item.expires_at = exp - 10; } @@ -221,9 +220,9 @@ export const dapUtils = { } if (refreshMembership) { if (config.segtax == 504) { - dapUtils.dapRefreshEncryptedMembership(config, token, onDone); + dapUtils.dapRefreshEncryptedMembership(ortb2, config, token, onDone); } else { - dapUtils.dapRefreshMembership(config, token, onDone); + dapUtils.dapRefreshMembership(ortb2, config, token, onDone); } } }, @@ -250,7 +249,7 @@ export const dapUtils = { return membership; }, - dapRefreshMembership: function(config, token, onDone) { + dapRefreshMembership: function(ortb2, config, token, onDone) { let now = Math.round(Date.now() / 1000.0); // in seconds let item = {} let configAsync = {...config}; @@ -268,14 +267,14 @@ export const dapUtils = { dapUtils.dapLog(item); let data = dapUtils.dapGetRtdObj(item, config.segtax) - dapUtils.checkAndAddRealtimeData(data, config.segtax); + dapUtils.checkAndAddRealtimeData(ortb2, data, config.segtax); onDone(); }, function(xhr, status, error, onDone) { logError('ERROR(' + error + '): failed to retrieve membership! ' + status); if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { dapRetryTokenize++; - dapUtils.dapRefreshToken(config, true, onDone); + dapUtils.dapRefreshToken(ortb2, config, true, onDone); } else { onDone(); } @@ -297,14 +296,14 @@ export const dapUtils = { return encMembership; }, - dapRefreshEncryptedMembership: function(config, token, onDone) { + dapRefreshEncryptedMembership: function(ortb2, config, token, onDone) { let now = Math.round(Date.now() / 1000.0); // in seconds let item = {}; let configAsync = {...config}; dapUtils.dapEncryptedMembership(configAsync, token, onDone, function(encToken, status, xhr, onDone) { item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; - let exp = dapUtils.dapExtractExpiryFromToken(encToken) + let exp = dapUtils.dapExtractExpiryFromToken(encToken); if (typeof exp == 'number') { item.expires_at = exp - 10; } @@ -314,14 +313,14 @@ export const dapUtils = { dapUtils.dapLog(item); let encData = dapUtils.dapGetEncryptedRtdObj(item, config.segtax); - dapUtils.checkAndAddRealtimeData(encData, config.segtax); + dapUtils.checkAndAddRealtimeData(ortb2, encData, config.segtax); onDone(); }, function(xhr, status, error, onDone) { logError('ERROR(' + error + '): failed to retrieve encrypted membership! ' + status); if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { dapRetryTokenize++; - dapUtils.dapRefreshToken(config, true, onDone); + dapUtils.dapRefreshToken(ortb2, config, true, onDone); } else { onDone(); } @@ -417,20 +416,19 @@ export const dapUtils = { return encData; }, - checkAndAddRealtimeData: function(data, segtax) { + checkAndAddRealtimeData: function(ortb2, data, segtax) { if (data.rtd) { - if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(data.rtd, 504)) { + if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(ortb2, data.rtd, 504)) { logMessage('DEBUG(handleInit): rtb Object already added'); } else { - addRealTimeData(data.rtd); + addRealTimeData(ortb2, data.rtd); } logInfo('DEBUG(checkAndAddRealtimeData) - 1'); } }, - checkIfSegmentsAlreadyExist: function(rtd, segtax) { + checkIfSegmentsAlreadyExist: function(ortb2, rtd, segtax) { let segmentsExist = false - let ortb2 = config.getConfig('ortb2') || {}; if (ortb2.user && ortb2.user.data && ortb2.user.data.length > 0) { for (let i = 0; i < ortb2.user.data.length; i++) { let element = ortb2.user.data[i] diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index 261fd9dee68..fe5a050f436 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -18,16 +18,21 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { let bids = []; let bidIds = []; + let eids; validBidRequests.forEach(bidRequest => { let sizes = prepareSizes(bidRequest.sizes) + if (bidRequest.userIdAsEids) { + eids = eids || bidRequest.userIdAsEids + } bids.push({ token: bidRequest.params.token, pos: bidRequest.params.pos, bidFloor: bidRequest.params.bidFloor, width: sizes[0].width, height: sizes[0].height, - impMediaType: getFormatType(bidRequest) + impMediaType: getFormatType(bidRequest), + adUnitCode: bidRequest.adUnitCode }) bidIds.push(bidRequest.bidId) }) @@ -38,8 +43,25 @@ export const spec = { requestId: bidderRequest.auctionId, signRequest: { bids, randomUUID: alkimiConfig && alkimiConfig.randomUUID }, bidIds, - referer: bidderRequest.refererInfo.referer, - signature: alkimiConfig && alkimiConfig.signature + referer: bidderRequest.refererInfo.page, + signature: alkimiConfig && alkimiConfig.signature, + schain: validBidRequests[0].schain, + cpp: config.getConfig('coppa') ? 1 : 0 + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdprConsent = { + consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : false, + consentString: bidderRequest.gdprConsent.consentString + } + } + + if (bidderRequest.uspConsent) { + payload.uspConsent = bidderRequest.uspConsent; + } + + if (eids) { + payload.eids = eids } const options = { diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index d1754936d7f..dae7784b3bc 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -13,19 +13,9 @@ const VAST_RXP = /^\s*<\??(?:vast|xml)/i; const TRACKING_ENDPOINT = 'https://1x1.a-mo.net/hbx/'; const AMUID_KEY = '__amuidpb'; -function getLocation (request) { - const refInfo = request.refererInfo; - if (refInfo == null) { - return parseUrl(location.href); - } - - if (refInfo.isAmp && refInfo.referer != null) { - return parseUrl(refInfo.referer) - } - - const topUrl = refInfo.numIframes > 0 && refInfo.stack[0] != null - ? refInfo.stack[0] : location.href; - return parseUrl(topUrl); +function getLocation(request) { + // TODO: does it make sense to fall back to window.location? + return parseUrl(request.refererInfo?.topmostLocation || window.location.href) }; const largestSize = (sizes, mediaTypes) => { @@ -243,15 +233,16 @@ export const spec = { gs: deepAccess(bidderRequest, 'gdprConsent.gdprApplies', ''), gc: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), u: deepAccess(bidderRequest, 'refererInfo.canonicalUrl', loc.href), + // TODO: are these referer values correct? do: loc.hostname, - re: deepAccess(bidderRequest, 'refererInfo.referer'), + re: deepAccess(bidderRequest, 'refererInfo.ref'), am: getUIDSafe(), usp: bidderRequest.uspConsent || '1---', smt: 1, d: '', m: createBidMap(bidRequests), cpp: config.getConfig('coppa') ? 1 : 0, - fpd2: config.getConfig('ortb2'), + fpd2: bidderRequest.ortb2, tmax: config.getConfig('bidderTimeout'), eids: values(bidRequests.reduce((all, bid) => { // we only want unique ones in here diff --git a/modules/amxIdSystem.js b/modules/amxIdSystem.js index 28323b01188..9dbab496f2c 100644 --- a/modules/amxIdSystem.js +++ b/modules/amxIdSystem.js @@ -5,11 +5,11 @@ * @module modules/amxIdSystem * @requires module:modules/userId */ -import { uspDataHandler } from '../src/adapterManager.js'; -import { ajaxBuilder } from '../src/ajax.js'; -import { submodule } from '../src/hook.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { deepAccess, getWindowTop, logError } from '../src/utils.js'; +import {uspDataHandler} from '../src/adapterManager.js'; +import {ajaxBuilder} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {deepAccess, logError} from '../src/utils.js'; const NAME = 'amxId'; const GVL_ID = 737; @@ -109,8 +109,9 @@ export const amxIdSubmodule = { const params = { tagId: deepAccess(config, 'params.tagId', ''), - ref: ref.referer, - u: ref.stack[0] || getWindowTop().location.href, + // TODO: are these referer values correct? + ref: ref.ref, + u: ref.location, v: '$prebid.version$', vg: '$$PREBID_GLOBAL$$', us_privacy: usp, diff --git a/modules/andBeyondMediaBidAdapter.js b/modules/andBeyondMediaBidAdapter.js new file mode 100644 index 00000000000..57c141dc2aa --- /dev/null +++ b/modules/andBeyondMediaBidAdapter.js @@ -0,0 +1,202 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'beyondmedia'; +const AD_URL = 'https://backend.andbeyond.media/pbjs'; +const SYNC_URL = 'https://cookies.andbeyond.media'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + placement.placementId = placementId; + placement.type = 'publisher'; + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && params.placementId); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } + + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/andBeyondMediaBidAdapter.md b/modules/andBeyondMediaBidAdapter.md new file mode 100644 index 00000000000..ebb44a7779c --- /dev/null +++ b/modules/andBeyondMediaBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: AndBeyond.Media Bidder Adapter +Module Type: AndBeyond.Media Bidder Adapter +Maintainer: sysengg@andbeyond.media +``` + +# Description + +Connects to AndBeyond.Media exchange for bids. +AndBeyond.Media bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'beyondmedia', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'beyondmedia', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'beyondmedia', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js index 7760aa2b47b..84552638421 100644 --- a/modules/aniviewBidAdapter.js +++ b/modules/aniviewBidAdapter.js @@ -106,11 +106,8 @@ function buildRequests(validBidRequests, bidderRequest) { if (s2sParams.AV_APPPKGNAME && !s2sParams.AV_URL) { s2sParams.AV_URL = s2sParams.AV_APPPKGNAME; } if (!s2sParams.AV_IDFA && !s2sParams.AV_URL) { - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - s2sParams.AV_URL = bidderRequest.refererInfo.referer; - } else { - s2sParams.AV_URL = window.location.href; - } + // TODO: does it make sense to fall back to window.location here? + s2sParams.AV_URL = bidderRequest?.refererInfo?.page || window.location.href; } if (s2sParams.AV_IDFA && !s2sParams.AV_AID) { s2sParams.AV_AID = s2sParams.AV_IDFA; } if (s2sParams.AV_AID && !s2sParams.AV_IDFA) { s2sParams.AV_IDFA = s2sParams.AV_AID; } @@ -206,7 +203,7 @@ function interpretResponse(serverResponse, bidRequest) { let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); if (xml && xml.getElementsByTagName('parsererror').length == 0) { let cpmData = getCpmData(xml); - if (cpmData && cpmData.cpm > 0) { + if (cpmData.cpm > 0) { bidResponse.requestId = bidRequest.data.bidId; bidResponse.ad = ''; bidResponse.cpm = cpmData.cpm; @@ -309,7 +306,7 @@ function getUserSyncs(syncOptions, serverResponses) { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo', 'didnavideo', 'ottadvisors'], + aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo', 'didnavideo', 'ottadvisors', 'pgammedia'], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid, buildRequests, diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index d7b6b7c4020..10593855c59 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,6 +1,8 @@ import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {parseDomain} from '../src/refererDetection.js'; const BIDDER_CODE = 'apacdex'; const ENDPOINT = 'https://useast.quantumdex.io/auction/pbjs' const USERSYNC = 'https://sync.quantumdex.io/usersync/pbjs' @@ -101,9 +103,10 @@ export const spec = { var pageUrl = _extractTopWindowUrlFromBidderRequest(bidderRequest); payload.site = {}; - payload.site.page = pageUrl + payload.site.page = pageUrl; payload.site.referrer = _extractTopWindowReferrerFromBidderRequest(bidderRequest); - payload.site.hostname = getDomain(pageUrl); + // TODO: does it make sense to fall back to window.location for the domain? + payload.site.hostname = bidderRequest.refererInfo?.domain || parseDomain(pageUrl); // Apply GDPR parameters to request. if (bidderRequest && bidderRequest.gdprConsent) { @@ -121,12 +124,12 @@ export const spec = { // Apply schain. if (schain) { - payload.schain = schain + payload.schain = schain; } // Apply eids. if (eids) { - payload.eids = eids + payload.eids = eids; } // Apply geo @@ -283,18 +286,8 @@ function _getDoNotTrack() { * @returns {string} */ function _extractTopWindowUrlFromBidderRequest(bidderRequest) { - if (config.getConfig('pageUrl')) { - return config.getConfig('pageUrl'); - } - if (deepAccess(bidderRequest, 'refererInfo.referer')) { - return bidderRequest.refererInfo.referer; - } - - try { - return window.top.location.href; - } catch (e) { - return window.location.href; - } + // TODO: does it make sense to fall back to window.location? + return bidderRequest?.refererInfo?.page || window.location.href; } /** @@ -304,34 +297,8 @@ function _extractTopWindowUrlFromBidderRequest(bidderRequest) { * @returns {string} */ function _extractTopWindowReferrerFromBidderRequest(bidderRequest) { - if (bidderRequest && deepAccess(bidderRequest, 'refererInfo.referer')) { - return bidderRequest.refererInfo.referer; - } - - try { - return window.top.document.referrer; - } catch (e) { - return window.document.referrer; - } -} - -/** - * Extracts the domain from given page url - * - * @param {string} url - * @returns {string} - */ -export function getDomain(pageUrl) { - if (config.getConfig('publisherDomain')) { - var publisherDomain = config.getConfig('publisherDomain'); - return publisherDomain.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#:]/)[0]; - } - - if (!pageUrl) { - return pageUrl; - } - - return pageUrl.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#:]/)[0]; + // TODO: does it make sense to fall back to window.document.referrer? + return bidderRequest?.refererInfo?.ref || window.document.referrer; } /** @@ -378,14 +345,4 @@ function getBidFloor(bid) { return null; } -function hasPurpose1Consent(gdprConsent) { - let result = true; - if (gdprConsent) { - if (gdprConsent.gdprApplies && gdprConsent.apiVersion === 2) { - result = !!(deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true); - } - } - return result; -} - registerBidder(spec); diff --git a/modules/appierAnalyticsAdapter.js b/modules/appierAnalyticsAdapter.js index afcf63ef2c1..b4081feaf92 100644 --- a/modules/appierAnalyticsAdapter.js +++ b/modules/appierAnalyticsAdapter.js @@ -1,5 +1,5 @@ import {ajax} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {getGlobal} from '../src/prebidGlobal.js'; diff --git a/modules/appierBidAdapter.js b/modules/appierBidAdapter.js index 1940233a0b4..12346d15130 100644 --- a/modules/appierBidAdapter.js +++ b/modules/appierBidAdapter.js @@ -43,7 +43,8 @@ export const spec = { const bidderApiUrl = `//${server}${BIDDER_API_ENDPOINT}` const payload = { 'bids': bidRequests, - 'refererInfo': bidderRequest.refererInfo, + // TODO: please do not pass internal data structures over to the network + 'refererInfo': bidderRequest.refererInfo.legacy, 'version': ADAPTER_VERSION }; return [{ diff --git a/modules/appnexusAnalyticsAdapter.js b/modules/appnexusAnalyticsAdapter.js deleted file mode 100644 index 868b317d7d4..00000000000 --- a/modules/appnexusAnalyticsAdapter.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * appnexus.js - AppNexus Prebid Analytics Adapter - */ - -import adapter from '../src/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; - -var appnexusAdapter = adapter({ - global: 'AppNexusPrebidAnalytics', - handler: 'on', - analyticsType: 'bundle' -}); - -adapterManager.registerAnalyticsAdapter({ - adapter: appnexusAdapter, - code: 'appnexus', - gvlid: 32 -}); - -export default appnexusAdapter; diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index aa5b604781d..1d69e69f555 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -10,6 +10,7 @@ import { getMaxValueFromArray, getMinValueFromArray, getParameterByName, + getUniqueIdentifierStr, isArray, isArrayOfNums, isEmpty, @@ -21,6 +22,7 @@ import { logInfo, logMessage, logWarn, + mergeDeep, transformBidderParamKeywords, getWindowFromDocument } from '../src/utils.js'; @@ -33,6 +35,8 @@ import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {getStorageManager} from '../src/storageManager.js'; import {bidderSettings} from '../src/bidderSettings.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'appnexus'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -92,7 +96,6 @@ export const spec = { gvlid: GVLID, aliases: [ { code: 'appnexusAst', gvlid: 32 }, - { code: 'brealtime' }, { code: 'emxdigital', gvlid: 183 }, { code: 'pagescience' }, { code: 'defymedia' }, @@ -100,7 +103,6 @@ export const spec = { { code: 'matomy' }, { code: 'featureforward' }, { code: 'oftmedia' }, - { code: 'districtm', gvlid: 144 }, { code: 'adasta' }, { code: 'beintoo', gvlid: 618 }, ], @@ -123,6 +125,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + const tags = bidRequests.map(bidToTag); const userObjBid = find(bidRequests, hasUserInfo); let userObj = {}; @@ -212,7 +217,7 @@ export const spec = { payload['iab_support'] = { omidpn: 'Appnexus', omidpv: '$prebid.version$' - } + }; } if (member > 0) { @@ -220,21 +225,38 @@ export const spec = { } if (appDeviceObjBid) { - payload.device = appDeviceObj + payload.device = appDeviceObj; } if (appIdObjBid) { payload.app = appIdObj; } - let auctionKeywords = config.getConfig('appnexusAuctionKeywords'); - if (isPlainObject(auctionKeywords)) { - let aucKeywords = transformBidderParamKeywords(auctionKeywords); + function grabOrtb2Keywords(ortb2Obj) { + const fields = ['site.keywords', 'site.content.keywords', 'user.keywords', 'app.keywords', 'app.content.keywords']; + let result = []; - if (aucKeywords.length > 0) { - aucKeywords.forEach(deleteValues); - } + fields.forEach(path => { + let keyStr = deepAccess(ortb2Obj, path); + if (isStr(keyStr)) result.push(keyStr); + }); + return result; + } - payload.keywords = aucKeywords; + // grab the ortb2 keyword data (if it exists) and convert from the comma list string format to object format + let ortb2 = deepClone(bidderRequest && bidderRequest.ortb2); + let ortb2KeywordsObjList = grabOrtb2Keywords(ortb2).map(keyStr => convertStringToKeywordsObj(keyStr)); + + let anAuctionKeywords = deepClone(config.getConfig('appnexusAuctionKeywords')) || {}; + // need to convert the string values into array of strings, to properly merge values with other existing keys later + Object.keys(anAuctionKeywords).forEach(k => { if (isStr(anAuctionKeywords[k]) || isNumber(anAuctionKeywords[k])) anAuctionKeywords[k] = [anAuctionKeywords[k]] }); + // combine all sources of keywords (converted from string comma list to object format) into one object (that combines the values for shared keys) + let mergedAuctionKeywrds = mergeDeep({}, anAuctionKeywords, ...ortb2KeywordsObjList); + + // convert to final format used by adserver + let auctionKeywords = transformBidderParamKeywords(mergedAuctionKeywrds); + if (auctionKeywords.length > 0) { + auctionKeywords.forEach(deleteValues); + payload.keywords = auctionKeywords; } if (config.getConfig('adpod.brandCategoryExclusion')) { @@ -262,17 +284,18 @@ export const spec = { } if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent + payload.us_privacy = bidderRequest.uspConsent; } if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { - rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + // TODO: are these the correct referer values? + rd_ref: encodeURIComponent(bidderRequest.refererInfo.topmostLocation), rd_top: bidderRequest.refererInfo.reachedTop, rd_ifs: bidderRequest.refererInfo.numIframes, rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - } - let pubPageUrl = config.getConfig('pageUrl'); + }; + let pubPageUrl = bidderRequest.refererInfo.canonicalUrl; if (isStr(pubPageUrl) && pubPageUrl !== '') { refererinfo.rd_can = pubPageUrl; } @@ -292,7 +315,6 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; - addUserId(eids, deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); @@ -386,7 +408,7 @@ export const spec = { }, getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (syncOptions.iframeEnabled && hasPurpose1Consent({gdprConsent})) { + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { return [{ type: 'iframe', url: 'https://acdn.adnxs.com/dmp/async_usersync.html' @@ -407,7 +429,7 @@ export const spec = { if (includes(s2sCfg.bidders, adUnit.bids[0].bidder)) { s2sEndpointUrl = deepAccess(s2sCfg, 'endpoint.p1Consent'); } - }) + }); } if (s2sEndpointUrl && s2sEndpointUrl.match('/openrtb2/prebid')) { @@ -441,18 +463,8 @@ export const spec = { } return params; - }, - - /** - * Add element selector to javascript tracker to improve native viewability - * @param {Bid} bid - */ - onBidWon: function (bid) { - if (bid.native) { - reloadViewabilityScriptWithCorrectParameters(bid); - } } -} +}; function isPopulatedArray(arr) { return !!(isArray(arr) && arr.length > 0); @@ -464,58 +476,9 @@ function deleteValues(keyPairObj) { } } -function reloadViewabilityScriptWithCorrectParameters(bid) { - let viewJsPayload = getAppnexusViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); - - if (viewJsPayload) { - let prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; - - let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload) - - let newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); - - // find iframe containing script tag - let frameArray = document.getElementsByTagName('iframe'); - - // boolean var to modify only one script. That way if there are muliple scripts, - // they won't all point to the same creative. - let modifiedAScript = false; - - // first, loop on all ifames - for (let i = 0; i < frameArray.length && !modifiedAScript; i++) { - let currentFrame = frameArray[i]; - try { - // IE-compatible, see https://stackoverflow.com/a/3999191/2112089 - let nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document; - - if (nestedDoc) { - // if the doc is present, we look for our jstracker - let scriptArray = nestedDoc.getElementsByTagName('script'); - for (let j = 0; j < scriptArray.length && !modifiedAScript; j++) { - let currentScript = scriptArray[j]; - if (currentScript.getAttribute('data-src') == jsTrackerSrc) { - currentScript.setAttribute('src', newJsTrackerSrc); - currentScript.setAttribute('data-src', ''); - if (currentScript.removeAttribute) { - currentScript.removeAttribute('data-src'); - } - modifiedAScript = true; - } - } - } - } catch (exception) { - // trying to access a cross-domain iframe raises a SecurityError - // this is expected and ignored - if (!(exception instanceof DOMException && exception.name === 'SecurityError')) { - // all other cases are raised again to be treated by the calling function - throw exception; - } - } - } - } -} - function strIsAppnexusViewabilityScript(str) { + if (!str || str === '') return false; + let regexMatchUrlStart = str.match(VIEWABILITY_URL_START); let viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; @@ -525,40 +488,6 @@ function strIsAppnexusViewabilityScript(str) { return str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr; } -function getAppnexusViewabilityScriptFromJsTrackers(jsTrackerArray) { - let viewJsPayload; - if (isStr(jsTrackerArray) && strIsAppnexusViewabilityScript(jsTrackerArray)) { - viewJsPayload = jsTrackerArray; - } else if (isArray(jsTrackerArray)) { - for (let i = 0; i < jsTrackerArray.length; i++) { - let currentJsTracker = jsTrackerArray[i]; - if (strIsAppnexusViewabilityScript(currentJsTracker)) { - viewJsPayload = currentJsTracker; - } - } - } - return viewJsPayload; -} - -function getViewabilityScriptUrlFromPayload(viewJsPayload) { - // extracting the content of the src attribute - // -> substring between src=" and " - let indexOfFirstQuote = viewJsPayload.indexOf('src="') + 5; // offset of 5: the length of 'src=' + 1 - let indexOfSecondQuote = viewJsPayload.indexOf('"', indexOfFirstQuote); - let jsTrackerSrc = viewJsPayload.substring(indexOfFirstQuote, indexOfSecondQuote); - return jsTrackerSrc; -} - -function hasPurpose1Consent(bidderRequest) { - let result = true; - if (bidderRequest && bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { - result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); - } - } - return result; -} - function formatRequest(payload, bidderRequest) { let request = []; let options = { @@ -567,14 +496,14 @@ function formatRequest(payload, bidderRequest) { let endpointUrl = URL; - if (!hasPurpose1Consent(bidderRequest)) { + if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { endpointUrl = URL_SIMPLE; } if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { options.customHeaders = { 'X-Is-Test': 1 - } + }; } if (payload.tags.length > MAX_IMPS_PER_REQUEST) { @@ -640,7 +569,9 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { */ function newBid(serverBid, rtbBid, bidderRequest) { const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); + const adId = getUniqueIdentifierStr(); const bid = { + adId: adId, requestId: serverBid.uuid, cpm: rtbBid.cpm, creativeId: rtbBid.creative_id, @@ -726,22 +657,22 @@ function newBid(serverBid, rtbBid, bidderRequest) { bid.vastUrl = rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url); break; } - } else if (rtbBid.rtb[NATIVE]) { + } else if (FEATURES.NATIVE && rtbBid.rtb[NATIVE]) { const nativeAd = rtbBid.rtb[NATIVE]; + let viewScript; - // setting up the jsTracker: - // we put it as a data-src attribute so that the tracker isn't called - // until we have the adId (see onBidWon) - let jsTrackerDisarmed = rtbBid.viewability.config.replace('src=', 'data-src='); + if (strIsAppnexusViewabilityScript(rtbBid.viewability.config)) { + let prebidParams = 'pbjs_adid=' + adId + ';pbjs_auc=' + bidRequest.adUnitCode; + viewScript = rtbBid.viewability.config.replace('dom_id=%native_dom_id%', prebidParams); + } let jsTrackers = nativeAd.javascript_trackers; - if (jsTrackers == undefined) { - jsTrackers = jsTrackerDisarmed; + jsTrackers = viewScript; } else if (isStr(jsTrackers)) { - jsTrackers = [jsTrackers, jsTrackerDisarmed]; + jsTrackers = [jsTrackers, viewScript]; } else { - jsTrackers.push(jsTrackerDisarmed); + jsTrackers.push(viewScript); } bid[NATIVE] = { @@ -812,7 +743,7 @@ function bidToTag(bid) { tag.code = bid.params.invCode; } tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.use_pmt_rule = bid.params.usePaymentRule || false; tag.prebid = true; tag.disable_psa = true; let bidFloor = getBidFloor(bid); @@ -850,13 +781,25 @@ function bidToTag(bid) { if (bid.params.externalImpId) { tag.external_imp_id = bid.params.externalImpId; } - if (!isEmpty(bid.params.keywords)) { - let keywords = transformBidderParamKeywords(bid.params.keywords); - if (keywords.length > 0) { - keywords.forEach(deleteValues); + let ortb2ImpKwStr = deepAccess(bid, 'ortb2Imp.ext.data.keywords'); + if ((isStr(ortb2ImpKwStr) && ortb2ImpKwStr !== '') || !isEmpty(bid.params.keywords)) { + // convert ortb2 from comma list string format to bid param object format + let ortb2ImpKwObj = convertStringToKeywordsObj(ortb2ImpKwStr); + + let bidParamsKwObj = (isPlainObject(bid.params.keywords)) ? deepClone(bid.params.keywords) : {}; + // need to convert the string values into an array of strings, to properly merge values with other existing keys later + Object.keys(bidParamsKwObj).forEach(k => { if (isStr(bidParamsKwObj[k]) || isNumber(bidParamsKwObj[k])) bidParamsKwObj[k] = [bidParamsKwObj[k]] }); + + // combine both sources of keywords into one merged object (that combines the values for shared keys) + let keywordsObj = mergeDeep({}, bidParamsKwObj, ortb2ImpKwObj); + + // convert to final format used by adserver + let keywordsUt = transformBidderParamKeywords(keywordsObj); + if (keywordsUt.length > 0) { + keywordsUt.forEach(deleteValues); + tag.keywords = keywordsUt; } - tag.keywords = keywords; } let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); @@ -864,7 +807,7 @@ function bidToTag(bid) { tag.gpid = gpid; } - if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) { + if (FEATURES.NATIVE && (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`))) { tag.ad_types.push(NATIVE); if (tag.sizes.length === 0) { tag.sizes = transformSizes([1, 1]); @@ -1254,4 +1197,35 @@ function convertKeywordsToString(keywords) { return result; } +// converts a comma separated list of keywords into the standard keyword object format used in appnexus bid params +// 'genre=rock,genre=pop,pets=dog,music' goes to { 'genre': ['rock', 'pop'], 'pets': ['dog'], 'music': [''] } +function convertStringToKeywordsObj(keyStr) { + let result = {}; + + // will split based on commas and will eat white space before/after the comma + let keywordList = keyStr.split(/\s*(?:,)\s*/); + keywordList.forEach(kw => { + // if = exists, then split + if (kw.indexOf('=') !== -1) { + let kwPair = kw.split('='); + let key = kwPair[0]; + let val = kwPair[1]; + + // then check for existing key in result > if so add value to the array > if not, add new key and create value array + if (result.hasOwnProperty(key)) { + result[key].push(val); + } else { + result[key] = [val]; + } + } else { + // make a key with '' value; if key already exists > don't add + if (!result.hasOwnProperty(kw)) { + result[kw] = ['']; + } + } + }); + + return result; +} + registerBidder(spec); diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js index b69fffb8b6b..30abb17bfde 100644 --- a/modules/apstreamBidAdapter.js +++ b/modules/apstreamBidAdapter.js @@ -2,6 +2,7 @@ import { generateUUID, deepAccess, createTrackPixelHtml, getDNT } from '../src/u import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const CONSTANTS = { DSU_KEY: 'apr_dsu', @@ -266,7 +267,7 @@ var dsuModule = (function() { return { readOrCreateDsu: readOrCreateDsu - } + }; })(); function serializeSizes(sizes) { @@ -342,7 +343,7 @@ function getBids(bids) { const bidId = bid.bidId; let mediaType = ''; - const mediaTypes = Object.keys(bid.mediaTypes) + const mediaTypes = Object.keys(bid.mediaTypes); switch (mediaTypes[0]) { case 'video': mediaType = 'v'; @@ -416,6 +417,8 @@ function isBidRequestValid(bid) { } function buildRequests(bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); const data = { med: encodeURIComponent(window.location.href), auid: bidderRequest.auctionId, diff --git a/modules/arteebeeBidAdapter.md b/modules/arteebeeBidAdapter.md deleted file mode 100644 index 4c178d722b1..00000000000 --- a/modules/arteebeeBidAdapter.md +++ /dev/null @@ -1,32 +0,0 @@ -# Overview - -``` -Module Name: Arteebee Bidder Adapter -Module Type: Bidder Adapter -Maintainer: jeffyecn@gmail.com -``` - -# Description - -Module that connects to Arteebee's demand source - -# Test Parameters -``` - var adUnits = [ - { - code: 'banner-ad-div', - sizes: [[300, 250]], - bids: [ - { - bidder: 'arteebee', - params: { - ssp: 'mock', - pub: 'prebidtest', - source: 'prebidtest', - test: true - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/asealBidAdapter.js b/modules/asealBidAdapter.js index 855aee65f34..abe0cf907ed 100644 --- a/modules/asealBidAdapter.js +++ b/modules/asealBidAdapter.js @@ -59,7 +59,8 @@ export const spec = { const data = { bids: validBidRequests, - refererInfo: bidderRequest.refererInfo, + // TODO: please do not pass internal data structures over to the network + refererInfo: bidderRequest.refererInfo?.legacy, device: { webSessionId: getTrekWebSessionId(), }, diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index bf45b9ee48f..9469bc6b00c 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -1,8 +1,19 @@ -import { _each, deepAccess, logWarn, tryAppendQueryString, inIframe, getWindowTop, parseUrl, parseSizesInput, isFn, getDNT, deepSetValue } from '../src/utils.js'; +import { + _each, + deepAccess, + deepSetValue, + getDNT, + inIframe, + isFn, + logWarn, + parseSizesInput, + tryAppendQueryString +} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; +import {parseDomain} from '../src/refererDetection.js'; const BIDDER_CODE = 'aso'; const DEFAULT_SERVER_URL = 'https://srv.aso1.net'; @@ -167,28 +178,13 @@ function createRenderer(bid, url) { } function getUrlsInfo(bidderRequest) { - let page = ''; - let referrer = ''; - - const {refererInfo} = bidderRequest; - - if (inIframe()) { - page = refererInfo.referer; - } else { - const w = getWindowTop(); - page = w.location.href; - referrer = w.document.referrer || ''; - } - - page = config.getConfig('pageUrl') || page; - const url = parseUrl(page); - const domain = url.hostname; - + const {page, domain, ref} = bidderRequest.refererInfo; return { - domain, - page, - referrer - }; + // TODO: do the fallbacks make sense here? + page: page || bidderRequest.refererInfo?.topmostLocation, + referrer: ref || '', + domain: domain || parseDomain(bidderRequest?.refererInfo?.topmostLocation) + } } function getSize(paramSizes) { diff --git a/modules/astraoneBidAdapter.js b/modules/astraoneBidAdapter.js index c233e665499..d6bfa4b93ee 100644 --- a/modules/astraoneBidAdapter.js +++ b/modules/astraoneBidAdapter.js @@ -99,7 +99,7 @@ export const spec = { */ buildRequests(validBidRequests, bidderRequest) { const payload = { - url: bidderRequest.refererInfo.referer, + url: bidderRequest.refererInfo.page, cmp: !!bidderRequest.gdprConsent, bidRequests: buildBidRequests(validBidRequests) }; diff --git a/modules/atomxBidAdapter.md b/modules/atomxBidAdapter.md deleted file mode 100644 index 7f32b12fdfe..00000000000 --- a/modules/atomxBidAdapter.md +++ /dev/null @@ -1,25 +0,0 @@ -# Overview -Module Name: Atomx Bidder Adapter Module -Type: Bidder Adapter -Maintainer: erik@atomx.com - -# Description -Atomx Bidder Adapter for Prebid.js. - -# Test Parameters -``` -var adUnits = [ -{ - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: 'atomx', - params: { - id: 4025860, - } - } - ] -} -]; -``` diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index f45d2e80055..0c0227ea34a 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -1,5 +1,5 @@ import { logError, logInfo } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adaptermanager from '../src/adapterManager.js'; import {ajax} from '../src/ajax.js'; @@ -267,7 +267,7 @@ function sendDataToAnalytic (events) { } // preflight request, to check did publisher have permission to send data to analytics endpoint -function preflightRequest (envelopeSourceCookieValue, events) { +function preflightRequest (events) { logInfo('ATS Analytics - preflight request!'); ajax(preflightUrl + atsAnalyticsAdapter.context.pid, { @@ -277,7 +277,7 @@ function preflightRequest (envelopeSourceCookieValue, events) { let samplingRate = samplingRateObject.samplingRate; atsAnalyticsAdapter.setSamplingCookie(samplingRate); let samplingRateNumber = Number(samplingRate); - if (data && samplingRate && atsAnalyticsAdapter.shouldFireRequest(samplingRateNumber) && envelopeSourceCookieValue != null) { + if (data && samplingRate && atsAnalyticsAdapter.shouldFireRequest(samplingRateNumber)) { logInfo('ATS Analytics - events to send: ', events); sendDataToAnalytic(events); } @@ -377,12 +377,11 @@ atsAnalyticsAdapter.callHandler = function (evtype, args) { } // check should we send data to analytics or not, check first cookie value _lr_sampling_rate try { - let envelopeSourceCookieValue = storage.getCookie('_lr_env_src_ats'); let samplingRateCookie = storage.getCookie('_lr_sampling_rate'); if (!samplingRateCookie) { - preflightRequest(envelopeSourceCookieValue, events); + preflightRequest(events); } else { - if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie)) && envelopeSourceCookieValue != null) { + if (atsAnalyticsAdapter.shouldFireRequest(parseInt(samplingRateCookie))) { logInfo('ATS Analytics - events to send: ', events); sendDataToAnalytic(events); } diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js index 2744e38e820..754a48ede75 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -71,12 +71,7 @@ function getPageReferer() { * @return {string} */ function getPageUrl(bidderRequest) { - return ( - config.getConfig('pageUrl') || - deepAccess(bidderRequest, 'refererInfo.referer') || - getPageReferer() || - null - ); + return bidderRequest?.refererInfo?.page } export const spec = { @@ -127,10 +122,12 @@ export const spec = { const payload = { libVersion: this.version, - pageUrl: config.getConfig('pageUrl'), + pageUrl: bidderRequest?.refererInfo?.page, + // TODO: does it make sense to find a half-way referer? what should these parameters pick pageReferer: getPageReferer(), - referer: deepAccess(bidderRequest, 'refererInfo.referer'), - refererInfo: deepAccess(bidderRequest, 'refererInfo'), + referer: deepAccess(bidderRequest, 'refererInfo.topmostLocation'), + // TODO: please do not send internal data structures over the network + refererInfo: deepAccess(bidderRequest, 'refererInfo.legacy'), currencyCode: config.getConfig('currency.adServerCurrency'), timeout: config.getConfig('bidderTimeout'), bids, diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js index d3aeb204d5e..1174c2a9f38 100644 --- a/modules/automatadBidAdapter.js +++ b/modules/automatadBidAdapter.js @@ -61,9 +61,9 @@ export const spec = { imp: impressions, site: { id: siteId, - domain: window.location.hostname, - page: window.location.href, - ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null, + domain: bidderRequest.refererInfo?.domain, + page: bidderRequest.refererInfo?.page, + ref: bidderRequest.refererInfo?.ref }, } diff --git a/modules/avocetBidAdapter.md b/modules/avocetBidAdapter.md deleted file mode 100644 index 95cb29303f2..00000000000 --- a/modules/avocetBidAdapter.md +++ /dev/null @@ -1,40 +0,0 @@ -# Overview - -``` -Module Name: Avocet Bidder Adapter -Module Type: Bidder Adapter -Maintainer: developers@avocet.io -``` - -# Description - -Module that connects to the Avocet advertising platform. - -# Parameters - -| Name | Scope | Description | Example | -| :------------ | :------- | :---------------------------------- | :------------------------- | -| `placement` | required | A Placement ID from Avocet. | "5ebd27607781b9af3ccc3332" | - - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300, 250]], // a display size - } - }, - bids: [ - { - bidder: "avct", - params: { - placement: "5ebd27607781b9af3ccc3332" - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index a790a89a0c1..5435bf09059 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -25,12 +25,11 @@ function getBidFloor(bidRequest) { } function getPageUrl(bidRequest, bidderRequest) { - let pageUrl = config.getConfig('pageUrl'); - + let pageUrl; if (bidRequest.params.referrer) { pageUrl = bidRequest.params.referrer; - } else if (!pageUrl) { - pageUrl = bidderRequest.refererInfo.referer; + } else { + pageUrl = bidderRequest.refererInfo.page; } return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 1c341e4dc51..f80481d66c8 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -9,7 +9,6 @@ import { parseSizesInput, parseUrl } from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -31,7 +30,7 @@ export const SUPPORTED_USER_IDS = [ { key: 'tdid', source: 'adserver.org', rtiPartner: 'TDID', queryParam: 'tdid' }, { key: 'idl_env', source: 'liveramp.com', rtiPartner: 'idl', queryParam: 'idl' }, { key: 'uid2.id', source: 'uidapi.com', rtiPartner: 'UID2', queryParam: 'uid2' }, - { key: 'haloId', source: 'audigent.com', atype: 1, queryParam: 'haloid' } + { key: 'hadronId', source: 'audigent.com', atype: 1, queryParam: 'hadronid' } ]; let appId = ''; @@ -305,16 +304,7 @@ function isBannerBidValid(bid) { } function getTopWindowLocation(bidderRequest) { - let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; - return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); -} - -function getTopWindowReferrer() { - try { - return window.top.document.referrer; - } catch (e) { - return ''; - } + return parseUrl(bidderRequest?.refererInfo?.page, { decodeSearchAsString: true }); } function getEids(bid) { @@ -369,7 +359,7 @@ function createVideoRequestData(bid, bidderRequest) { let tagid = getVideoBidParam(bid, 'tagid'); let topLocation = getTopWindowLocation(bidderRequest); let eids = getEids(bid); - let ortb2 = deepClone(config.getConfig('ortb2')); + let ortb2 = deepClone(bidderRequest.ortb2); let payload = { isPrebid: true, appId: appId, @@ -433,7 +423,7 @@ function createVideoRequestData(bid, bidderRequest) { function createBannerRequestData(bids, bidderRequest) { let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(); + let topReferrer = bidderRequest.refererInfo?.ref; let slots = bids.map(bid => { return { slot: bid.adUnitCode, @@ -443,7 +433,7 @@ function createBannerRequestData(bids, bidderRequest) { sizes: getBannerSizes(bid) }; }); - let ortb2 = deepClone(config.getConfig('ortb2')); + let ortb2 = deepClone(bidderRequest.ortb2); let payload = { slots: slots, ortb2: ortb2, diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index ba960838395..f8a53a293de 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -38,7 +38,6 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { const slots = validBidRequests.map(beOpRequestSlotsMaker); const pageUrl = getPageUrl(bidderRequest.refererInfo, window); - const fpd = config.getLegacyFpd(config.getConfig('ortb2')); const gdpr = bidderRequest.gdprConsent; const firstSlot = slots[0]; const payloadObject = { @@ -48,7 +47,7 @@ export const spec = { pid: firstSlot.pid, url: pageUrl, lang: (window.navigator.language || window.navigator.languages[0]), - kwds: (fpd && fpd.site && fpd.site.keywords) || [], + kwds: bidderRequest.ortb2?.site?.keywords || [], dbg: false, slts: slots, is_amp: deepAccess(bidderRequest, 'referrerInfo.isAmp'), @@ -100,12 +99,12 @@ export const spec = { } function buildTrackingParams(data, info, value) { - const accountId = data.params.accountId; + let params = Array.isArray(data.params) ? data.params[0] : data.params; const pageUrl = getPageUrl(null, window); return { - pid: accountId === undefined ? data.ad.match(/account: \“([a-f\d]{24})\“/)[1] : accountId, - nid: data.params.networkId, - nptnid: data.params.networkPartnerId, + pid: params.accountId === undefined ? data.ad.match(/account: \“([a-f\d]{24})\“/)[1] : params.accountId, + nid: params.networkId, + nptnid: params.networkPartnerId, bid: data.bidId || data.requestId, sl_n: data.adUnitCode, aid: data.auctionId, diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index e4907c15974..ea28420481d 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -1,7 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { getAdUnitSizes, parseSizesInput } from '../src/utils.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import {includes} from '../src/polyfill.js' +import {getAdUnitSizes, parseSizesInput} from '../src/utils.js'; +import {includes} from '../src/polyfill.js'; const BIDDER_CODE = 'between'; let ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid'; @@ -29,7 +28,7 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { let requests = []; const gdprConsent = bidderRequest && bidderRequest.gdprConsent; - const refInfo = getRefererInfo(); + const refInfo = bidderRequest?.refererInfo; validBidRequests.forEach((i) => { const video = i.mediaTypes && i.mediaTypes.video; @@ -60,9 +59,9 @@ export const spec = { if (i.params.itu !== undefined) { params.itu = i.params.itu; } - if (i.params.cur !== undefined) { - params.cur = i.params.cur; - } + + params.cur = i.params.cur || 'USD'; + if (i.params.subid !== undefined) { params.subid = i.params.subid; } @@ -79,7 +78,8 @@ export const spec = { params.schain = encodeToBase64WebSafe(JSON.stringify(i.schain)); } - if (refInfo && refInfo.referer) params.ref = refInfo.referer; + // TODO: is 'page' the right value here? + if (refInfo && refInfo.page) params.ref = refInfo.page; if (gdprConsent) { if (typeof gdprConsent.gdprApplies !== 'undefined') { @@ -90,7 +90,7 @@ export const spec = { } } - requests.push({data: params}) + requests.push({data: params}); }) return { method: 'POST', diff --git a/modules/bidViewability.js b/modules/bidViewability.js index 837eccd00c1..362401e6d1c 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -68,7 +68,7 @@ export let impressionViewableHandler = (globalModuleConfig, slot, event) => { // if config is enabled AND VURL array is present then execute each pixel fireViewabilityPixels(globalModuleConfig, respectiveBid); // trigger respective bidder's onBidViewable handler - adapterManager.callBidViewableBidder(respectiveBid.bidder, respectiveBid); + adapterManager.callBidViewableBidder(respectiveBid.adapterCode || respectiveBid.bidder, respectiveBid); // emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, respectiveBid); } diff --git a/modules/bidfluenceBidAdapter.md b/modules/bidfluenceBidAdapter.md deleted file mode 100644 index 34dbb3d3a1c..00000000000 --- a/modules/bidfluenceBidAdapter.md +++ /dev/null @@ -1,31 +0,0 @@ -# Overview - -``` -Module Name: Bidfluence Adapter -Module Type: Bidder Adapter -Maintainer: integrations@bidfluence.com -prebid_1_0_supported : true -gdpr_supported: true -``` - -# Description - -Bidfluence adapter for prebid. - -# Test Parameters - -``` -var adUnits = [ - { - code: 'test-prebid', - sizes: [[300, 250]], - bids: [{ - bidder: 'bidfluence', - params: { - placementId: '1000', - publisherId: '1000' - } - }] - } -] -``` diff --git a/modules/bidlabBidAdapter.md b/modules/bidlabBidAdapter.md deleted file mode 100644 index 3e5fe3128ed..00000000000 --- a/modules/bidlabBidAdapter.md +++ /dev/null @@ -1,53 +0,0 @@ -# Overview - -``` -Module Name: bidlab Bidder Adapter -Module Type: bidlab Bidder Adapter -``` - -# Description - -Module that connects to bidlab demand sources - -# Test Parameters -``` - var adUnits = [ - // Will return static test banner - { - code: 'placementId_0', - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - bids: [ - { - bidder: 'bidlab', - params: { - placementId: 0, - traffic: 'banner' - } - } - ] - }, - // Will return test vast xml. All video params are stored under placement in publishers UI - { - code: 'placementId_0', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'instream' - } - }, - bids: [ - { - bidder: 'bidlab', - params: { - placementId: 0, - traffic: 'video' - } - } - ] - } - ]; -``` diff --git a/modules/bidphysicsBidAdapter.md b/modules/bidphysicsBidAdapter.md deleted file mode 100644 index d7d8b355027..00000000000 --- a/modules/bidphysicsBidAdapter.md +++ /dev/null @@ -1,33 +0,0 @@ -# Overview - -``` -Module Name: BidPhysics Bid Adapter -Module Type: Bidder Adapter -Maintainer: info@bidphysics.com -``` - -# Description - -Connects to BidPhysics exchange for bids. - -BidPhysics bid adapter supports Banner ads. - -# Test Parameters -``` -var adUnits = [ - { - code: 'banner-ad-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: 'bidphysics', - params: { - unitId: 'bidphysics-test' - } - }] - } -]; -``` diff --git a/modules/bidscubeBidAdapter.js b/modules/bidscubeBidAdapter.js index 951bd97d255..6cdbba61c75 100644 --- a/modules/bidscubeBidAdapter.js +++ b/modules/bidscubeBidAdapter.js @@ -1,6 +1,7 @@ import { logMessage, getWindowLocation } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js' +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'bidscube' const URL = 'https://supply.bidscube.com/?c=o&m=multi' @@ -15,6 +16,9 @@ export const spec = { }, buildRequests: function (validBidRequests) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + validBidRequests = validBidRequests || [] let winTop = window try { diff --git a/modules/bidwatchAnalyticsAdapter.js b/modules/bidwatchAnalyticsAdapter.js index 26a8c370af3..b94b7eccada 100644 --- a/modules/bidwatchAnalyticsAdapter.js +++ b/modules/bidwatchAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; @@ -10,50 +10,161 @@ const { EVENTS: { AUCTION_END, BID_WON, + BID_RESPONSE, + BID_REQUESTED, + BID_TIMEOUT, } } = CONSTANTS; +let saveEvents = {} let allEvents = {} +let auctionEnd = {} let initOptions = {} let endpoint = 'https://default' -let objectToSearchForBidderCode = ['bidderRequests', 'bidsReceived', 'noBids'] +let requestsAttributes = ['adUnitCode', 'auctionId', 'bidder', 'bidderCode', 'bidId', 'cpm', 'creativeId', 'currency', 'width', 'height', 'mediaType', 'netRevenue', 'originalCpm', 'originalCurrency', 'requestId', 'size', 'source', 'status', 'timeToRespond', 'transactionId', 'ttl', 'sizes', 'mediaTypes', 'src', 'params', 'userId', 'labelAny', 'bids']; function getAdapterNameForAlias(aliasName) { return adapterManager.aliasRegistry[aliasName] || aliasName; } -function setOriginalBidder(arg) { - Object.keys(arg).forEach(key => { - arg[key]['originalBidder'] = getAdapterNameForAlias(arg[key]['bidderCode']); - if (typeof arg[key]['creativeId'] == 'number') { arg[key]['creativeId'] = arg[key]['creativeId'].toString(); } +function filterAttributes(arg, removead) { + let response = {}; + if (typeof arg == 'object') { + if (typeof arg['bidderCode'] == 'string') { + response['originalBidder'] = getAdapterNameForAlias(arg['bidderCode']); + } else if (typeof arg['bidder'] == 'string') { + response['originalBidder'] = getAdapterNameForAlias(arg['bidder']); + } + if (!removead && typeof arg['ad'] != 'undefined') { + response['ad'] = arg['ad']; + } + if (typeof arg['gdprConsent'] != 'undefined') { + response['gdprConsent'] = {}; + if (typeof arg['gdprConsent']['consentString'] != 'undefined') { response['gdprConsent']['consentString'] = arg['gdprConsent']['consentString']; } + } + if (typeof arg['meta'] == 'object' && typeof arg['meta']['advertiserDomains'] != 'undefined') { + response['meta'] = {'advertiserDomains': arg['meta']['advertiserDomains']}; + } + requestsAttributes.forEach((attr) => { + if (typeof arg[attr] != 'undefined') { response[attr] = arg[attr]; } + }); + if (typeof response['creativeId'] == 'number') { response['creativeId'] = response['creativeId'].toString(); } + } + return response; +} + +function cleanAuctionEnd(args) { + let response = {}; + let filteredObj; + let objects = ['bidderRequests', 'bidsReceived', 'noBids', 'adUnits']; + objects.forEach((attr) => { + if (Array.isArray(args[attr])) { + response[attr] = []; + args[attr].forEach((obj) => { + filteredObj = filterAttributes(obj, true); + if (typeof obj['bids'] == 'object') { + filteredObj['bids'] = []; + obj['bids'].forEach((bid) => { + filteredObj['bids'].push(filterAttributes(bid, true)); + }); + } + response[attr].push(filteredObj); + }); + } }); - return arg + return response; +} + +function cleanCreatives(args) { + return filterAttributes(args, false); } -function checkBidderCode(args) { - if (typeof args == 'object') { - for (let i = 0; i < objectToSearchForBidderCode.length; i++) { - if (typeof args[objectToSearchForBidderCode[i]] == 'object') { args[objectToSearchForBidderCode[i]] = setOriginalBidder(args[objectToSearchForBidderCode[i]]) } +function enhanceMediaType(arg) { + saveEvents['bidRequested'].forEach((bidRequested) => { + if (bidRequested['auctionId'] == arg['auctionId'] && Array.isArray(bidRequested['bids'])) { + bidRequested['bids'].forEach((bid) => { + if (bid['transactionId'] == arg['transactionId'] && bid['bidId'] == arg['requestId']) { arg['mediaTypes'] = bid['mediaTypes']; } + }); } - } - if (typeof args['bidderCode'] == 'string') { args['originalBidder'] = getAdapterNameForAlias(args['bidderCode']); } else if (typeof args['bidder'] == 'string') { args['originalBidder'] = getAdapterNameForAlias(args['bidder']); } - if (typeof args['creativeId'] == 'number') { args['creativeId'] = args['creativeId'].toString(); } - return args + }); + return arg; } -function addEvent(eventType, args) { +function addBidResponse(args) { + let eventType = BID_RESPONSE; + let argsCleaned = cleanCreatives(JSON.parse(JSON.stringify(args))); ; if (allEvents[eventType] == undefined) { allEvents[eventType] = [] } - if (eventType && args) { args = checkBidderCode(args); } - allEvents[eventType].push(args); + allEvents[eventType].push(argsCleaned); +} + +function addBidRequested(args) { + let eventType = BID_REQUESTED; + let argsCleaned = filterAttributes(args, true); + if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } + saveEvents[eventType].push(argsCleaned); +} + +function addTimeout(args) { + let eventType = BID_TIMEOUT; + if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } + saveEvents[eventType].push(args); + let argsCleaned = []; + let argsDereferenced = JSON.parse(JSON.stringify(args)); + argsDereferenced.forEach((attr) => { + argsCleaned.push(filterAttributes(JSON.parse(JSON.stringify(attr)), false)); + }); + if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } + auctionEnd[eventType].push(argsCleaned); +} + +function addAuctionEnd(args) { + let eventType = AUCTION_END; + if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] } + saveEvents[eventType].push(args); + let argsCleaned = cleanAuctionEnd(JSON.parse(JSON.stringify(args))); + if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } + auctionEnd[eventType].push(argsCleaned); } function handleBidWon(args) { - if (typeof allEvents.bidRequested == 'object' && allEvents.bidRequested.length > 0 && allEvents.bidRequested[0].gdprConsent) { args.gdpr = allEvents.bidRequested[0].gdprConsent; } + args = enhanceMediaType(filterAttributes(JSON.parse(JSON.stringify(args)), true)); + let increment = args['cpm']; + if (typeof saveEvents['auctionEnd'] == 'object') { + saveEvents['auctionEnd'].forEach((auction) => { + if (auction['auctionId'] == args['auctionId'] && typeof auction['bidsReceived'] == 'object') { + auction['bidsReceived'].forEach((bid) => { + if (bid['transactionId'] == args['transactionId'] && bid['adId'] != args['adId']) { + if (args['cpm'] < bid['cpm']) { + increment = 0; + } else if (increment > args['cpm'] - bid['cpm']) { + increment = args['cpm'] - bid['cpm']; + } + } + }); + } + }); + } + args['cpmIncrement'] = increment; + if (typeof saveEvents.bidRequested == 'object' && saveEvents.bidRequested.length > 0 && saveEvents.bidRequested[0].gdprConsent) { args.gdpr = saveEvents.bidRequested[0].gdprConsent; } ajax(endpoint + '.bidwatch.io/analytics/bid_won', null, JSON.stringify(args), {method: 'POST', withCredentials: true}); } function handleAuctionEnd() { - ajax(endpoint + '.bidwatch.io/analytics/auctions', null, JSON.stringify(allEvents), {method: 'POST', withCredentials: true}); + ajax(endpoint + '.bidwatch.io/analytics/auctions', function (data) { + let list = JSON.parse(data); + if (Array.isArray(list) && typeof allEvents['bidResponse'] != 'undefined') { + let alreadyCalled = []; + allEvents['bidResponse'].forEach((bidResponse) => { + let tmpId = bidResponse['originalBidder'] + '_' + bidResponse['creativeId']; + if (list.includes(tmpId) && !alreadyCalled.includes(tmpId)) { + alreadyCalled.push(tmpId); + ajax(endpoint + '.bidwatch.io/analytics/creatives', null, JSON.stringify(bidResponse), {method: 'POST', withCredentials: true}); + } + }); + } + allEvents = {}; + }, JSON.stringify(auctionEnd), {method: 'POST', withCredentials: true}); + auctionEnd = {}; } let bidwatchAnalytics = Object.assign(adapter({url, analyticsType}), { @@ -61,14 +172,23 @@ let bidwatchAnalytics = Object.assign(adapter({url, analyticsType}), { eventType, args }) { - addEvent(eventType, args); switch (eventType) { case AUCTION_END: + addAuctionEnd(args); handleAuctionEnd(); break; case BID_WON: handleBidWon(args); break; + case BID_RESPONSE: + addBidResponse(args); + break; + case BID_REQUESTED: + addBidRequested(args); + break; + case BID_TIMEOUT: + addTimeout(args); + break; } }}); diff --git a/modules/big-richmediaBidAdapter.js b/modules/big-richmediaBidAdapter.js index 2ee31e8cfd6..8a03aac1ace 100644 --- a/modules/big-richmediaBidAdapter.js +++ b/modules/big-richmediaBidAdapter.js @@ -8,7 +8,7 @@ const BIDDER_CODE = 'big-richmedia'; const metadataByRequestId = {}; export const spec = { - version: '1.5.0', + version: '1.5.1', code: BIDDER_CODE, gvlid: baseAdapter.GVLID, // use base adapter gvlid supportedMediaTypes: [ BANNER, VIDEO ], @@ -82,8 +82,8 @@ export const spec = { // This is a workaround needed for the rendering step (so that the adserver iframe does not get resized to 1800x1000 // when there is skin demand if (format === 'skin') { - renderParams.width = 1 - renderParams.height = 1 + bid.width = 1 + bid.height = 1 } const encoded = window.btoa(JSON.stringify(renderParams)); diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index a798671cbaf..4ef2b6dd9f8 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -2,6 +2,7 @@ import { logMessage, getDNT, deepSetValue, deepAccess, _map, logWarn } from '../ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'bizzclick'; const ACCOUNTID_MACROS = '[account_id]'; const URL_ENDPOINT = `https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=${ACCOUNTID_MACROS}`; @@ -57,13 +58,16 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + if (validBidRequests && validBidRequests.length === 0) return [] let accuontId = validBidRequests[0].params.accountId; const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); let winTop = window; let location; try { - location = new URL(bidderRequest.refererInfo.referer) + location = new URL(bidderRequest.refererInfo.page) winTop = window.top; } catch (e) { location = winTop.location; @@ -252,7 +256,7 @@ const addNativeParameters = bidRequest => { wmin = sizes[0]; hmin = sizes[1]; } - asset[props.name] = {} + asset[props.name] = {}; if (bidParams.len) asset[props.name]['len'] = bidParams.len; if (props.type) asset[props.name]['type'] = props.type; if (wmin) asset[props.name]['wmin'] = wmin; diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 45b6c46c2df..127b534c989 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -1,10 +1,12 @@ // eslint-disable-next-line prebid/validate-imports // eslint-disable-next-line prebid/validate-imports -import {registerBidder} from '../src/adapters/bidderFactory.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { config } from '../src/config.js' +import {_each, deepAccess, deepSetValue} from '../src/utils.js' export const BIDDER_CODE = 'bliink' -export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/delivery' -export const BLIINK_ENDPOINT_ENGINE_VAST = 'https://engine.bliink.io/vast' -export const BLIINK_ENDPOINT_COOKIE_SYNC = 'https://cookiesync.api.bliink.io' +export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/prebid' + +export const BLIINK_ENDPOINT_COOKIE_SYNC_IFRAME = 'https://tag.bliink.io/usersync.html' export const META_KEYWORDS = 'keywords' export const META_DESCRIPTION = 'description' @@ -14,6 +16,13 @@ const BANNER = 'banner' const supportedMediaTypes = [BANNER, VIDEO] const aliasBidderCode = ['bk'] +/** + * @description get coppa value from config + */ +function getCoppa() { + return config.getConfig('coppa') === true ? 1 : 0; +} + export function getMetaList(name) { if (!name || name.length === 0) return [] @@ -52,7 +61,7 @@ export function getOneMetaValue(query) { return metaEl.content } - return null + return null; } export function getMetaValue(name) { @@ -75,79 +84,51 @@ export function getKeywords() { ] if (keywords && keywords.length > 0) { - return keywords - .filter((value) => value) - .map((value) => value.trim()) + return keywords.filter((value) => value).map((value) => value.trim()); } } - return [] -} - -export const parseXML = (content) => { - if (typeof content !== 'string' || content.length === 0) return null - - const parser = new DOMParser() - let xml; - - try { - xml = parser.parseFromString(content, 'text/xml') - } catch (e) {} - - if (xml && - xml.getElementsByTagName('VAST')[0] && - xml.getElementsByTagName('VAST')[0].tagName === 'VAST') { - return xml - } - - return null + return []; } /** * @param bidRequest - * @param bliinkCreative - * @return {{cpm, netRevenue: boolean, requestId, width: (*|number), currency, ttl: number, creativeId, height: (*|number)} & {mediaType: string, vastXml}} + * @return {({cpm, netRevenue: boolean, requestId, width: number, currency, ttl: number, creativeId, height: number}&{mediaType: string, vastXml})|null} */ -export const buildBid = (bidRequest, bliinkCreative) => { - if (!bidRequest && !bliinkCreative) return null - - const body = { - requestId: bidRequest.bidId, - currency: bliinkCreative.currency, - cpm: bliinkCreative.price, - creativeId: bliinkCreative.creativeId, - width: (bidRequest.sizes && bidRequest.sizes[0][0]) || 1, - height: (bidRequest.sizes && bidRequest.sizes[0][1]) || 1, - netRevenue: false, - ttl: 3600, - } - - // eslint-disable-next-line no-mixed-operators - if ((bliinkCreative) && bidRequest && - // eslint-disable-next-line no-mixed-operators - !bidRequest.bidId || - !bidRequest.sizes || - !bidRequest.params || - !(bidRequest.params.placement) - ) return null - - delete bidRequest['bids'] +export const buildBid = (bidResponse) => { + const mediaType = deepAccess(bidResponse, 'creative.media_type'); + if (!mediaType) return null; - switch (bliinkCreative.media_type) { + let bid; + switch (mediaType) { case VIDEO: - return Object.assign(body, { - mediaType: VIDEO, - vastXml: bliinkCreative.content, - }) + const vastXml = deepAccess(bidResponse, 'creative.video.content'); + bid = { + vastXml, + mediaType: 'video', + vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(vastXml.replace(/\\"/g, '"')) + }; + break; case BANNER: - return Object.assign(body, { - mediaType: BANNER, - ad: (bliinkCreative && bliinkCreative.content && bliinkCreative.content.creative && bliinkCreative.content.creative.adm) || '', - }) - default: + bid = { + ad: deepAccess(bidResponse, 'creative.banner.adm'), + mediaType: 'banner', + }; break; + default: + return null; } -} + return Object.assign(bid, { + cpm: bidResponse.price, + currency: bidResponse.currency || 'EUR', + creativeId: deepAccess(bidResponse, 'extras.deal_id'), + requestId: deepAccess(bidResponse, 'extras.transaction_id'), + width: deepAccess(bidResponse, `creative.${bid.mediaType}.width`) || 1, + height: deepAccess(bidResponse, `creative.${bid.mediaType}.height`) || 1, + ttl: 3600, + netRevenue: true, + }); +}; /** * @description Verify the the AdUnits.bids, respond with true (valid) or false (invalid). @@ -156,60 +137,58 @@ export const buildBid = (bidRequest, bliinkCreative) => { * @return boolean */ export const isBidRequestValid = (bid) => { - return !(!bid || !bid.params || !bid.params.placement || !bid.params.tagId) -} + return !!deepAccess(bid, 'params.tagId'); +}; /** * @description Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. * - * @param _[] + * @param validBidRequests * @param bidderRequest - * @return {{ method: string, url: string } | null} + * @returns {null|{method: string, data: {gdprConsent: string, keywords: string, pageTitle: string, pageDescription: (*|string), pageUrl, gdpr: boolean, tags: *}, url: string}} */ -export const buildRequests = (_, bidderRequest) => { - if (!bidderRequest) return null +export const buildRequests = (validBidRequests, bidderRequest) => { + if (!validBidRequests || !bidderRequest || !bidderRequest.bids) return null - let data = { - pageUrl: bidderRequest.refererInfo.referer, + const tags = bidderRequest.bids.map((bid) => { + return { + sizes: bid.sizes.map((size) => ({ w: size[0], h: size[1] })), + id: bid.params.tagId, + transactionId: bid.bidId, + mediaTypes: Object.keys(bid.mediaTypes), + imageUrl: deepAccess(bid, 'params.imageUrl', ''), + }; + }); + + let request = { + tags, + pageTitle: document.title, + pageUrl: deepAccess(bidderRequest, 'refererInfo.page'), pageDescription: getMetaValue(META_DESCRIPTION), keywords: getKeywords().join(','), - gdpr: false, - gdpr_consent: '', - pageTitle: document.title, + }; + const schain = deepAccess(validBidRequests[0], 'schain') + if (schain) { + request.schain = schain } - - const endPoint = bidderRequest.bids[0].params.placement === VIDEO ? BLIINK_ENDPOINT_ENGINE_VAST : BLIINK_ENDPOINT_ENGINE - - const params = { - bidderRequestId: bidderRequest.bidderRequestId, - bidderCode: bidderRequest.bidderCode, - bids: bidderRequest.bids, - refererInfo: bidderRequest.refererInfo, + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent'); + if (!!gdprConsent && gdprConsent.gdprApplies) { + request.gdpr = true + deepSetValue(request, 'gdprConsent', gdprConsent.consentString); } - - if (bidderRequest.gdprConsent) { - data = Object.assign(data, { - gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies, - gdpr_consent: bidderRequest.gdprConsent.consentString - }) + if (config.getConfig('coppa')) { + request.coppa = 1 } - - if (bidderRequest.bids && bidderRequest.bids.length > 0 && bidderRequest.bids[0].sizes && bidderRequest.bids[0].sizes[0]) { - data = Object.assign(data, { - width: bidderRequest.bids[0].sizes[0][0], - height: bidderRequest.bids[0].sizes[0][1] - }) - - return { - method: 'GET', - url: `${endPoint}/${bidderRequest.bids[0].params.tagId}`, - data: data, - params: params, - } + if (bidderRequest.uspConsent) { + deepSetValue(request, 'uspConsent', bidderRequest.uspConsent); } - return null -} + return { + method: 'POST', + url: BLIINK_ENDPOINT_ENGINE, + data: request, + }; +}; /** * @description Parse the response (from buildRequests) and generate one or more bid objects. @@ -218,51 +197,15 @@ export const buildRequests = (_, bidderRequest) => { * @param request * @return */ -const interpretResponse = (serverResponse, request) => { - if ((serverResponse && serverResponse.mode === 'no-ad')) { - return [] - } - - const body = serverResponse.body - const serverBody = request.params - - const xml = parseXML(body) - - let creative; - - switch (serverBody.bids[0].params.placement) { - case xml && VIDEO: - const price = xml.getElementsByTagName('Price') && xml.getElementsByTagName('Price')[0] - const currency = xml.getElementsByTagName('Currency') && xml.getElementsByTagName('Currency')[0] - const creativeId = xml.getElementsByTagName('CreativeId') && xml.getElementsByTagName('CreativeId')[0] - - creative = { - content: body, - price: (price && price.textContent) || 0, - currency: (currency && currency.textContent) || 'EUR', - creativeId: creativeId || 0, - media_type: 'video', - } - - return buildBid(serverBody.bids[0], creative) - case BANNER: - if (body) { - creative = { - content: body, - price: body.price, - currency: body.currency, - creativeId: 0, - media_type: 'banner', - } - - return buildBid(serverBody.bids[0], creative) - } - - break - default: - break - } -} +const interpretResponse = (serverResponse) => { + const bodyResponse = deepAccess(serverResponse, 'body.bids') + if (!serverResponse.body || !bodyResponse) return [] + const bidResponses = []; + _each(bodyResponse, function (response) { + return bidResponses.push(buildBid(response)); + }); + return bidResponses.filter(bid => !!bid) +}; /** * @description If the publisher allows user-sync activity, the platform will call this function and the adapter may register pixels and/or iframe user syncs. For more information, see Registering User Syncs below @@ -271,54 +214,39 @@ const interpretResponse = (serverResponse, request) => { * @param gdprConsent * @return {[{type: string, url: string}]|*[]} */ -const getUserSyncs = (syncOptions, serverResponses, gdprConsent) => { - let syncs = [] - +const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncs = []; if (syncOptions.pixelEnabled && serverResponses.length > 0) { + let gdprParams = '' + let uspConsentStr = '' + let apiVersion + let gdpr = false if (gdprConsent) { - const gdprParams = `consentString=${gdprConsent.consentString}` - const smartCallbackURL = encodeURIComponent(`${BLIINK_ENDPOINT_COOKIE_SYNC}/cookiesync?partner=smart&uid=[sas_uid]`) - const azerionCallbackURL = encodeURIComponent(`${BLIINK_ENDPOINT_COOKIE_SYNC}/cookiesync?partner=azerion&uid={PUB_USER_ID}`) - const appnexusCallbackURL = encodeURIComponent(`${BLIINK_ENDPOINT_COOKIE_SYNC}/cookiesync?partner=azerion&uid=$UID`) - return [ - { - type: 'script', - url: 'https://prg.smartadserver.com/ac?out=js&nwid=3392&siteid=305791&pgname=rg&fmtid=81127&tgt=[sas_target]&visit=m&tmstp=[timestamp]&clcturl=[countgo]' - }, - { - type: 'image', - url: `https://sync.smartadserver.com/getuid?nwid=3392&${gdprParams}&url=${smartCallbackURL}`, - }, - { - type: 'image', - url: `https://ad.360yield.com/server_match?partner_id=1531&${gdprParams}&r=${azerionCallbackURL}`, - }, - { - type: 'image', - url: `https://ads.stickyadstv.com/auto-user-sync?${gdprParams}`, - }, - { - type: 'image', - url: `https://cookiesync.api.bliink.io/getuid?url=https%3A%2F%2Fvisitor.omnitagjs.com%2Fvisitor%2Fsync%3Fuid%3D1625272249969090bb9d544bd6d8d645%26name%3DBLIINK%26visitor%3D%24UID%26external%3Dtrue&${gdprParams}`, - }, - { - type: 'image', - url: `https://cookiesync.api.bliink.io/getuid?url=https://pixel.advertising.com/ups/58444/sync?&gdpr=1&gdpr_consent=${gdprConsent.consentString}&redir=true&uid=$UID`, - }, - { - type: 'image', - url: `https://ups.analytics.yahoo.com/ups/58499/occ?gdpr=1&gdpr_consent=${gdprConsent.consentString}`, - }, + gdprParams = `&gdprConsent=${gdprConsent.consentString}`; + apiVersion = `&apiVersion=${gdprConsent.apiVersion}` + gdpr = Number( + gdprConsent.gdprApplies) + } + if (uspConsent) { + uspConsentStr = `&uspConsent=${uspConsent}`; + } + let sync; + if (syncOptions.iframeEnabled) { + sync = [ { - type: 'image', - url: `https://secure.adnxs.com/getuid?${appnexusCallbackURL}`, + type: 'iframe', + url: `${BLIINK_ENDPOINT_COOKIE_SYNC_IFRAME}?gdpr=${gdpr}&coppa=${getCoppa()}${uspConsentStr}${gdprParams}${apiVersion}`, }, - ] + ]; + } else { + sync = deepAccess(serverResponses[0], 'body.userSyncs'); } + + return sync; } return syncs; -} +}; /** * @type {{interpretResponse: interpretResponse, code: string, aliases: string[], getUserSyncs: getUserSyncs, buildRequests: buildRequests, onTimeout: onTimeout, onSetTargeting: onSetTargeting, isBidRequestValid: isBidRequestValid, onBidWon: onBidWon}} @@ -331,6 +259,6 @@ export const spec = { buildRequests, interpretResponse, getUserSyncs, -} +}; -registerBidder(spec) +registerBidder(spec); diff --git a/modules/bliinkBidAdapter.md b/modules/bliinkBidAdapter.md index af7aee3a1ae..48b95a10ebb 100644 --- a/modules/bliinkBidAdapter.md +++ b/modules/bliinkBidAdapter.md @@ -3,10 +3,10 @@ ``` Module Name: BLIINK Bidder Adapter Module Type: Bidder Adapter -Maintainer: samuel@bliink.io | jonathan@bliink.io +Maintainer: samuel@bliink.io | ibrahima@bliink.io gdpr_supported: true tcf2_supported: true -media_types: banner, native, video +media_types: banner, video ``` # Description @@ -30,7 +30,6 @@ const adUnits = [ { bidder: 'bliink', params: { - placement: 'banner', tagId: '41' } } @@ -58,7 +57,6 @@ const adUnits = [ bidder: 'bliink', params: { tagId: '41', - placement: 'video', } } ] @@ -85,7 +83,6 @@ const adUnits = [ bidder: 'bliink', params: { tagId: '41', - placement: 'video', } } ] diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js index d362dfa5fdb..27e310177f6 100644 --- a/modules/bluebillywigBidAdapter.js +++ b/modules/bluebillywigBidAdapter.js @@ -306,7 +306,7 @@ export const spec = { if (getConfig('coppa') == true) deepSetValue(request, 'regs.coppa', 1); // Enrich the request with any external data we may have - BB_HELPERS.addSiteAppDevice(request, bidderRequest.refererInfo && bidderRequest.refererInfo.referer); + BB_HELPERS.addSiteAppDevice(request, bidderRequest.refererInfo && bidderRequest.refererInfo.page); BB_HELPERS.addSchain(request, validBidRequests); BB_HELPERS.addCurrency(request); BB_HELPERS.addUserIds(request, validBidRequests); diff --git a/modules/blueconicRtdProvider.js b/modules/blueconicRtdProvider.js new file mode 100644 index 00000000000..9a7f5984637 --- /dev/null +++ b/modules/blueconicRtdProvider.js @@ -0,0 +1,94 @@ +/** + * This module adds the blueconic provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch real-time data from Blueconic + * @module modules/blueconicRtdProvider + * @requires module:modules/realTimeData + */ + +import {getStorageManager} from '../src/storageManager.js'; +import {submodule} from '../src/hook.js'; +import {mergeDeep, isPlainObject, logMessage, logError} from '../src/utils.js'; + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'blueconic'; + +export const RTD_LOCAL_NAME = 'bcPrebidData'; + +export const storage = getStorageManager({moduleName: SUBMODULE_NAME}); + +/** +* Try parsing stringified array of data. +* @param {String} data +*/ +function parseJson(data) { + try { + return JSON.parse(data); + } catch (err) { + logError(`blueconicRtdProvider: failed to parse json:`, data); + return null; + } +} + +/** + * Add real-time data & merge segments. + * @param {Object} bidConfig + * @param {Object} rtd + * @param {Object} rtdConfig + */ +export function addRealTimeData(ortb2, rtd) { + if (isPlainObject(rtd.ortb2)) { + mergeDeep(ortb2, rtd.ortb2); + } +} + +/** + * Real-time data retrieval from BlueConic + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} rtdConfig + * @param {Object} userConsent + */ +export function getRealTimeData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { + if (rtdConfig && isPlainObject(rtdConfig.params)) { + const jsonData = storage.getDataFromLocalStorage(RTD_LOCAL_NAME); + if (jsonData) { + const parsedData = parseJson(jsonData); + if (!parsedData) { + return; + } + const userData = {name: 'blueconic', ...parsedData} + logMessage('blueconicRtdProvider: userData: ', userData); + const data = { + ortb2: { + user: { + data: [ + userData + ] + } + } + } + addRealTimeData(reqBidsConfigObj.ortb2Fragments?.global, data); + onDone(); + } + } +} + +/** + * Module init + * @param {Object} provider + * @param {Objkect} userConsent + * @return {boolean} + */ +function init(provider, userConsent) { + return true; +} + +/** @type {RtdSubmodule} */ +export const blueconicSubmodule = { + name: SUBMODULE_NAME, + getBidRequestData: getRealTimeData, + init: init +}; + +submodule(MODULE_NAME, blueconicSubmodule); diff --git a/modules/blueconicRtdProvider.md b/modules/blueconicRtdProvider.md new file mode 100644 index 00000000000..b28bc6468fb --- /dev/null +++ b/modules/blueconicRtdProvider.md @@ -0,0 +1,90 @@ +# Overview + +coppa_supported: true (COPPA support) + +Module Name: BlueConic Rtd Provider +Module Type: Rtd Provider +Maintainer: connectors@blueconic.com + + +## BlueConic Real-time Data Submodule + +The BlueConic real-time data module in Prebid has been built so that publishers +can maximize the power of their first-party audiences, user-level and contextual data. +This module provides both an integrated BlueConic identity with real-time +contextual and audience segmentation solution that seamlessly and easily +integrates into your existing Prebid deployment. + +BlueConic's Real-time Data Provider automatically obtains segmentation data and other user level data from the BlueConic script (via `localStorage`) and passes them to the bid-stream. Please reach out to BlueConic team(info@blueconic.com) or visit our [website](https://support.blueconic.com/hc/en-us) if you have any questions or need further help to integrate Prebid or blueconicRtdProvider. + +### Publisher Usage + +Compile the BlueConic RTD module into your Prebid build: + +`gulp build --modules=rtdModule,blueconicRtdProvider,appnexusBidAdapter` + +Add the BlueConic RTD provider to your Prebid config. In this example we will configure +publisher 1234 to retrieve segments, profile data from BlueConic. See the +"Parameter Descriptions" below for more detailed information of the +configuration parameters. Please work with your BlueConic Prebid support team +(info@blueconic.com) on which version of Prebid.js supports different bidder +and segment configurations. + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "blueconic", + waitForIt: true, + params: { + requestParams: { + publisherId: 1234, + coppa: true + } + } + } + ] + } + ... +} +``` + +### Parameter Descriptions for the Blueconic Configuration Section + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Always 'blueconic' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | | +| params.requestParams | Object | Publisher partner specific configuration options, such as optional publisher id, coppa config and other segment query related metadata | Optional | + + +Please see the examples available in the blueconicRtdProvider_spec.js +tests and work with your Blueconic Prebid integration team (connectors@blueconic.com). + +#### COPPA support + +COPPA support can be enabled for all the visitors by changing the config value: + +```js +config.setConfig({ coppa: true }); +``` + +### Testing + +To run test suite for blueconic: + +`gulp test --modules=rtdModule,blueconicRtdProvider,appnexusBidAdapter` + +### Example + +To view an example of available segments: + +`gulp serve --modules=rtdModule,blueconicRtdProvider,appnexusBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/blueconicRtdProvider_example.html` diff --git a/modules/boldwinBidAdapter.js b/modules/boldwinBidAdapter.js index fcff7134a92..3915df8b976 100644 --- a/modules/boldwinBidAdapter.js +++ b/modules/boldwinBidAdapter.js @@ -1,6 +1,7 @@ import { isFn, deepAccess, logMessage } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'boldwin'; const AD_URL = 'https://ssp.videowalldirect.com/pbjs'; @@ -45,14 +46,18 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); + return Boolean(bid.bidId && bid.params && (bid.params.placementId || bid.params.endpointId)); }, buildRequests: (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let winTop = window; let location; + // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin try { - location = new URL(bidderRequest.refererInfo.referer) + location = new URL(bidderRequest.refererInfo.page); winTop = window.top; } catch (e) { location = winTop.location; @@ -73,23 +78,23 @@ export const spec = { request.ccpa = bidderRequest.uspConsent; } if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent + request.gdpr = bidderRequest.gdprConsent; } } const len = validBidRequests.length; for (let i = 0; i < len; i++) { let bid = validBidRequests[i]; - const { mediaTypes } = bid; + const { mediaTypes, params } = bid; const placement = {}; let sizes; if (mediaTypes) { if (mediaTypes[BANNER] && mediaTypes[BANNER].sizes) { placement.adFormat = BANNER; - sizes = mediaTypes[BANNER].sizes + sizes = mediaTypes[BANNER].sizes; } else if (mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize) { placement.adFormat = VIDEO; - sizes = mediaTypes[VIDEO].playerSize + sizes = mediaTypes[VIDEO].playerSize; placement.minduration = mediaTypes[VIDEO].minduration; placement.maxduration = mediaTypes[VIDEO].maxduration; placement.mimes = mediaTypes[VIDEO].mimes; @@ -109,9 +114,18 @@ export const spec = { placement.native = mediaTypes[NATIVE]; } } + + const { placementId, endpointId } = params; + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + placements.push({ ...placement, - placementId: bid.params.placementId, bidId: bid.bidId, sizes: sizes || [], wPlayer: sizes ? sizes[0] : 0, diff --git a/modules/boldwinBidAdapter.md b/modules/boldwinBidAdapter.md index 5e2a5b139b3..47f4b4ffe78 100644 --- a/modules/boldwinBidAdapter.md +++ b/modules/boldwinBidAdapter.md @@ -47,5 +47,22 @@ Module that connects to boldwin demand sources } ] } + // Will return static test banner + { + code: 'endpointId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'boldwin', + params: { + endpointId: 'testBanner', + } + } + ] + }, ]; ``` diff --git a/modules/brainyBidAdapter.md b/modules/brainyBidAdapter.md deleted file mode 100644 index 0f8308f6cc3..00000000000 --- a/modules/brainyBidAdapter.md +++ /dev/null @@ -1,31 +0,0 @@ -# Overview - -``` -Module Name: brainy Bid Adapter -Module Type: Bidder Adapter -Maintainer: support@mg.brainy-inc.co.jp -``` - -# Description -This module connects to brainy's demand sources. It supports display, and rich media formats. -brainy will provide ``accountID`` and ``slotID`` that are specific to your ad type. -Please reach out to ``support@mg.brainy-inc.co.jp`` to set up an brainy account and above ids. -Use bidder code ```brainy``` for all brainy traffic. - - -# Test Parameters - -``` - var adUnits = [{ - code: 'test-div', - sizes: [[300, 250], - bids: [{ - bidder: 'brainy', - params: { - accountID: "3481", - slotID: "5569" - } - }] - } - ]; -``` diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js index 60d3c98f15e..53868eccc4c 100644 --- a/modules/brandmetricsRtdProvider.js +++ b/modules/brandmetricsRtdProvider.js @@ -5,10 +5,10 @@ * @module modules/brandmetricsRtdProvider * @requires module:modules/realTimeData */ -import { config } from '../src/config.js' -import { submodule } from '../src/hook.js' -import { deepSetValue, mergeDeep, logError, deepAccess } from '../src/utils.js' -import {loadExternalScript} from '../src/adloader.js' +import {submodule} from '../src/hook.js'; +import {deepAccess, deepSetValue, logError, mergeDeep} from '../src/utils.js'; +import {loadExternalScript} from '../src/adloader.js'; + const MODULE_NAME = 'brandmetrics' const MODULE_CODE = MODULE_NAME const RECEIVED_EVENTS = [] @@ -109,11 +109,8 @@ function processBrandmetricsEvents (reqBidsConfigObj, moduleConfig, callback) { function setBidderTargeting (reqBidsConfigObj, moduleConfig, key, val) { const bidders = deepAccess(moduleConfig, 'params.bidders') if (bidders && bidders.length > 0) { - const ortb2 = {} - deepSetValue(ortb2, 'ortb2.user.ext.data.' + key, val) - config.setBidderConfig({ - bidders: bidders, - config: ortb2 + bidders.forEach(bidder => { + deepSetValue(reqBidsConfigObj, `ortb2Fragments.bidder.${bidder}.user.ext.data.${key}`, val); }) } } diff --git a/modules/braveBidAdapter.js b/modules/braveBidAdapter.js index 18bad6b0f75..2e087b41868 100644 --- a/modules/braveBidAdapter.js +++ b/modules/braveBidAdapter.js @@ -2,6 +2,7 @@ import { parseUrl, isEmpty, isStr, triggerPixel } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'brave'; const DEFAULT_CUR = 'USD'; @@ -38,6 +39,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + if (validBidRequests.length === 0 || !bidderRequest) return []; const endpointURL = ENDPOINT_URL.replace('hash', validBidRequests[0].params.placementId); @@ -62,23 +66,9 @@ export const spec = { return impObject; }); - let w = window; - let l = w.document.location.href; - let r = w.document.referrer; - - let loopChecker = 0; - while (w !== w.parent) { - if (++loopChecker == 10) break; - try { - w = w.parent; - l = w.location.href; - r = w.document.referrer; - } catch (e) { - break; - } - } - - let page = l || bidderRequest.refererInfo.referer; + // TODO: do these values make sense? + let page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + let r = bidderRequest.refererInfo.ref; let data = { id: bidderRequest.bidderRequestId, diff --git a/modules/bridgewellBidAdapter.js b/modules/bridgewellBidAdapter.js index b141763af8e..6088cefaa55 100644 --- a/modules/bridgewellBidAdapter.js +++ b/modules/bridgewellBidAdapter.js @@ -2,6 +2,7 @@ import {_each, deepSetValue, inIframe} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {find} from '../src/polyfill.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'bridgewell'; const REQUEST_ENDPOINT = 'https://prebid.scupio.com/recweb/prebid.aspx?cb='; @@ -36,6 +37,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const adUnits = []; var bidderUrl = REQUEST_ENDPOINT + Math.random(); var userIds; @@ -72,7 +76,7 @@ export const spec = { let topUrl = ''; if (bidderRequest && bidderRequest.refererInfo) { - topUrl = bidderRequest.refererInfo.referer; + topUrl = bidderRequest.refererInfo.page; } return { @@ -85,9 +89,10 @@ export const spec = { }, inIframe: inIframe(), url: topUrl, - referrer: getTopWindowReferrer(), + referrer: bidderRequest.refererInfo.ref, adUnits: adUnits, - refererInfo: bidderRequest.refererInfo, + // TODO: please do not send internal data structures over the network + refererInfo: bidderRequest.refererInfo.legacy, }, validBidRequests: validBidRequests }; @@ -289,12 +294,4 @@ export const spec = { } }; -function getTopWindowReferrer() { - try { - return window.top.document.referrer; - } catch (e) { - return ''; - } -} - registerBidder(spec); diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js index d3ae1d9cf43..6db06744c24 100644 --- a/modules/brightMountainMediaBidAdapter.js +++ b/modules/brightMountainMediaBidAdapter.js @@ -99,7 +99,7 @@ export const spec = { let response; try { - response = serverResponse.body + response = serverResponse.body; bid = response.seatbid[0].bid[0]; } catch (e) { response = null; @@ -149,6 +149,7 @@ export const spec = { registerBidder(spec); function buildSite(bidderRequest) { + // TODO: should name/domain be the domain? let site = { name: window.location.hostname, publisher: { @@ -160,12 +161,12 @@ function buildSite(bidderRequest) { deepSetValue( site, 'page', - bidderRequest.refererInfo.referer.href ? bidderRequest.refererInfo.referer.href : '', + bidderRequest.refererInfo.page ); deepSetValue( site, 'ref', - bidderRequest.refererInfo.referer ? bidderRequest.refererInfo.referer : '', + bidderRequest.refererInfo.ref ); } return site; diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index 4895f303973..64b3c3a9fc8 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -1,4 +1,4 @@ -import { getBidIdParameter, _each, isArray, getWindowTop, getUniqueIdentifierStr, parseUrl, deepSetValue, logError, logWarn, createTrackPixelHtml, getWindowSelf, isFn, isPlainObject } from '../src/utils.js'; +import { getBidIdParameter, _each, isArray, getWindowTop, getUniqueIdentifierStr, deepSetValue, logError, logWarn, createTrackPixelHtml, getWindowSelf, isFn, isPlainObject } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -19,7 +19,7 @@ function buildRequests(bidReqs, bidderRequest) { try { let referrer = ''; if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.referer; + referrer = bidderRequest.refererInfo.page; } const brightcomImps = []; const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); @@ -56,7 +56,7 @@ function buildRequests(bidReqs, bidderRequest) { id: getUniqueIdentifierStr(), imp: brightcomImps, site: { - domain: parseUrl(referrer).host, + domain: bidderRequest?.refererInfo?.domain || '', page: referrer, publisher: { id: publisherId diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 15f2d58010d..0996fc9905b 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -57,6 +57,16 @@ export function addBrowsiTag(data) { return script; } +export function sendPageviewEvent(eventType) { + if (eventType === 'PAGEVIEW') { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + vendor: 'browsi', + type: 'pageview', + billingId: generateUUID() + }) + } +} + /** * collect required data from page * send data to browsi server to get predictions @@ -93,7 +103,7 @@ export function collectData() { function waitForData(callback) { if (_browsiData) { _dataReadyCallback = null; - callback(_browsiData); + callback(); } else { _dataReadyCallback = callback; } @@ -102,7 +112,7 @@ function waitForData(callback) { export function setData(data) { _browsiData = data; if (isFn(_dataReadyCallback)) { - _dataReadyCallback(_browsiData); + _dataReadyCallback(); _dataReadyCallback = null; } } @@ -262,10 +272,11 @@ function getPredictionsFromServer(url) { try { const data = JSON.parse(response); if (data && data.p && data.kn) { - setData({p: data.p, kn: data.kn, pmd: data.pmd}); + setData({p: data.p, kn: data.kn, pmd: data.pmd, bet: data.bet}); } else { setData({}); } + sendPageviewEvent(data.bet); addBrowsiTag(data); } catch (err) { logError('unable to parse data'); @@ -336,19 +347,22 @@ export const browsiSubmodule = { function getTargetingData(uc, c, us, a) { const targetingData = getRTD(uc); - const auctionId = a.auctionId + const auctionId = a.auctionId; + const sendAdRequestEvent = (_browsiData && _browsiData['bet'] === 'AD_REQUEST'); uc.forEach(auc => { if (isNumber(_ic[auc])) { _ic[auc] = _ic[auc] + 1; } - const transactionId = a.adUnits.find(adUnit => adUnit.code === auc).transactionId; - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { - vendor: 'browsi', - type: 'adRequest', - billingId: generateUUID(), - transactionId: transactionId, - auctionId: auctionId - }) + if (sendAdRequestEvent) { + const transactionId = a.adUnits.find(adUnit => adUnit.code === auc).transactionId; + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + vendor: 'browsi', + type: 'adRequest', + billingId: generateUUID(), + transactionId: transactionId, + auctionId: auctionId + }) + } }); logInfo('Browsi RTD provider returned targeting data', targetingData, 'for', uc) return targetingData; diff --git a/modules/buzzoolaBidAdapter.js b/modules/buzzoolaBidAdapter.js index c6e27c94e04..b5ea6227f58 100644 --- a/modules/buzzoolaBidAdapter.js +++ b/modules/buzzoolaBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {OUTSTREAM} from '../src/video.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'buzzoola'; const ENDPOINT = 'https://exchange.buzzoola.com/ssp/prebidjs'; @@ -32,6 +33,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidderRequest.bids = convertOrtbRequestToProprietaryNative(bidderRequest.bids); + return { url: ENDPOINT, method: 'POST', diff --git a/modules/byDataAnalyticsAdapter.js b/modules/byDataAnalyticsAdapter.js index ef6e1a503ee..84479ee618b 100644 --- a/modules/byDataAnalyticsAdapter.js +++ b/modules/byDataAnalyticsAdapter.js @@ -2,41 +2,83 @@ import { deepClone, logInfo, logError } from '../src/utils.js'; import Base64 from 'crypto-js/enc-base64'; import hmacSHA512 from 'crypto-js/hmac-sha512'; import enc from 'crypto-js/enc-utf8'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { auctionManager } from '../src/auctionManager.js'; import { ajax } from '../src/ajax.js'; -const secretKey = 'bydata@123456'; -const { EVENTS: { NO_BID, BID_TIMEOUT, AUCTION_END } } = CONSTANTS; -const DEFAULT_EVENT_URL = 'https://pbjs-stream.bydata.com/topics/prebid'; -const analyticsType = 'endpoint'; -var payload = {}; -var bdNbTo = { 'to': [], 'nb': [] }; -let initOptions = {}; +const versionCode = '4.4.1' +const secretKey = 'bydata@123456' +const { EVENTS: { NO_BID, BID_TIMEOUT, AUCTION_END, AUCTION_INIT, BID_WON } } = CONSTANTS +const DEFAULT_EVENT_URL = 'https://pbjs-stream.bydata.com/topics/prebid' +const analyticsType = 'endpoint' +const isBydata = isKeyInUrl('bydata_debug') +const adunitsMap = {} +const storage = getStorageManager(); +let initOptions = {} +var payload = {} +var winPayload = {} +var isDataSend = window.asc_data || false +var bdNbTo = { 'to': [], 'nb': [] } +/* method used for testing parameters */ +function isKeyInUrl(name) { + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const param = urlParams.get(name) + return param +} + +/* return ad unit full path wrt custom ad unit code */ +function getAdunitName(code) { + var name = code; + for (const [key, value] of Object.entries(adunitsMap)) { + if (key === code) { name = value; } + } + return name; +} + +/* EVENT: auction init */ +function onAuctionStart(t) { + /* map of ad unit code - ad unit full path */ + t.adUnits && t.adUnits.length && t.adUnits.forEach((adu) => { + const { code, adunit } = adu + adunitsMap[code] = adunit + }); +} + +/* EVENT: bid timeout */ function onBidTimeout(t) { if (payload['visitor_data'] && t && t.length > 0) { - bdNbTo['to'] = t; + bdNbTo['to'] = t } } +/* EVENT: no bid */ function onNoBidData(t) { if (payload['visitor_data'] && t) { - bdNbTo['nb'].push(t); + bdNbTo['nb'].push(t) + } +} + +/* EVENT: bid won */ +function onBidWon(t) { + const { isCorrectOption } = initOptions + if (isCorrectOption && (isDataSend || isBydata)) { + ascAdapter.getBidWonData(t) + ascAdapter.sendPayload(winPayload) } } +/* EVENT: auction end */ function onAuctionEnd(t) { - _logInfo('onAuctionEnd', t); - const {isCorrectOption, logFrequency} = initOptions; - var value = Math.floor(Math.random() * 10000 + 1); - _logInfo(' value - frequency ', (value + '-' + logFrequency)); + const { isCorrectOption } = initOptions; setTimeout(() => { - if (isCorrectOption && value < logFrequency) { + if (isCorrectOption && (isDataSend || isBydata)) { ascAdapter.dataProcess(t); - addKeyForPrebidWinningAndWinningsBids(); - ascAdapter.sendPayload(); + ascAdapter.sendPayload(payload); } }, 500); } @@ -44,6 +86,9 @@ function onAuctionEnd(t) { const ascAdapter = Object.assign(adapter({ url: DEFAULT_EVENT_URL, analyticsType: analyticsType }), { track({ eventType, args }) { switch (eventType) { + case AUCTION_INIT: + onAuctionStart(args); + break; case NO_BID: onNoBidData(args); break; @@ -53,6 +98,9 @@ const ascAdapter = Object.assign(adapter({ url: DEFAULT_EVENT_URL, analyticsType case AUCTION_END: onAuctionEnd(args); break; + case BID_WON: + onBidWon(args); + break; default: break; } @@ -62,9 +110,8 @@ const ascAdapter = Object.assign(adapter({ url: DEFAULT_EVENT_URL, analyticsType // save the base class function ascAdapter.originEnableAnalytics = ascAdapter.enableAnalytics; // override enableAnalytics so we can get access to the config passed in from the page -ascAdapter.enableAnalytics = function(config) { +ascAdapter.enableAnalytics = function (config) { if (this.initConfig(config)) { - _logInfo('initiated:', initOptions); initOptions.isCorrectOption && ascAdapter.getVisitorData(); ascAdapter.originEnableAnalytics(config); } @@ -73,7 +120,7 @@ ascAdapter.enableAnalytics = function(config) { ascAdapter.initConfig = function (config) { let isCorrectOption = true; initOptions = {}; - _logInfo('initConfig', config); + var rndNum = Math.floor(Math.random() * 10000 + 1); initOptions.options = deepClone(config.options); initOptions.clientId = initOptions.options.clientId || null; initOptions.logFrequency = initOptions.options.logFrequency; @@ -81,13 +128,41 @@ ascAdapter.initConfig = function (config) { _logError('"options.clientId" should not empty!!'); isCorrectOption = false; } + if (rndNum <= initOptions.logFrequency) { window.asc_data = isDataSend = true; } initOptions.isCorrectOption = isCorrectOption; this.initOptions = initOptions; return isCorrectOption; }; -ascAdapter.getVisitorData = function(data = {}) { - var ua = data.userId ? data : {}; +ascAdapter.getBidWonData = function(t) { + const { auctionId, adUnitCode, size, requestId, bidder, timeToRespond, currency, mediaType, cpm } = t + const aun = getAdunitName(adUnitCode) + winPayload['aid'] = auctionId + winPayload['as'] = ''; + winPayload['auctionData'] = []; + var data = {} + data['au'] = aun + data['auc'] = adUnitCode + data['aus'] = size + data['bid'] = requestId + data['bidadv'] = bidder + data['br_pb_mg'] = cpm + data['br_tr'] = timeToRespond + data['bradv'] = bidder + data['brid'] = requestId + data['brs'] = size + data['cur'] = currency + data['inb'] = 0 + data['ito'] = 0 + data['ipwb'] = 1 + data['iwb'] = 1 + data['mt'] = mediaType + winPayload['auctionData'].push(data) + return winPayload +} + +ascAdapter.getVisitorData = function (data = {}) { + var ua = data.uid ? data : {}; var module = { options: [], header: [window.navigator.platform, window.navigator.userAgent, window.navigator.appVersion, window.navigator.vendor, window.opera], @@ -152,7 +227,7 @@ ascAdapter.getVisitorData = function(data = {}) { crypto.getRandomValues(buffer); buffer[6] = (buffer[6] & ~176) | 64; buffer[8] = (buffer[8] & ~64) | 128; - var hex = Array.prototype.map.call(new Uint8Array(buffer), function(x) { + var hex = Array.prototype.map.call(new Uint8Array(buffer), function (x) { return ('00' + x.toString(16)).slice(-2); }).join(''); return hex.slice(0, 5) + '-' + hex.slice(5, 9) + '-' + hex.slice(9, 13) + '-' + hex.slice(13, 18); @@ -182,34 +257,46 @@ ascAdapter.getVisitorData = function(data = {}) { var signedToken = token + '.' + signature; return signedToken; } - const {clientId} = initOptions; - var userId = window.localStorage.getItem('userId'); + function detectWidth() { + return window.screen.width || (window.innerWidth && document.documentElement.clientWidth) ? Math.min(window.innerWidth, document.documentElement.clientWidth) : window.innerWidth || document.documentElement.clientWidth || document.getElementsByTagName('body')[0].clientWidth; + } + function giveDeviceTypeOnScreenSize() { + var _dWidth = detectWidth(); + return _dWidth > 1024 ? 'Desktop' : (_dWidth <= 1024 && _dWidth >= 768) ? 'Tablet' : 'Mobile'; + } + + const { clientId } = initOptions; + var userId = storage.getDataFromLocalStorage('userId'); if (!userId) { userId = generateUid(); - window.localStorage.setItem('userId', userId); + storage.setDataInLocalStorage('userId', userId); } - var screenSize = {width: window.screen.width, height: window.screen.height}; - var deviceType = window.navigator.userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i) ? 'Mobile' : 'Desktop'; + var screenSize = { width: window.screen.width, height: window.screen.height }; + var deviceType = giveDeviceTypeOnScreenSize(); var e = module.init(); - if (!ua['userId']) { - ua['userId'] = userId; - ua['client_id'] = clientId; - ua['plateform_name'] = e.os.name; - ua['os_version'] = e.os.version; - ua['browser_name'] = e.browser.name; - ua['browser_version'] = e.browser.version; - ua['screen_size'] = screenSize; - ua['device_type'] = deviceType; - ua['time_zone'] = window.Intl.DateTimeFormat().resolvedOptions().timeZone; + if (!ua['uid']) { + ua['uid'] = userId; + ua['cid'] = clientId; + ua['pid'] = window.location.hostname; + ua['os'] = e.os.name; + ua['osv'] = e.os.version; + ua['br'] = e.browser.name; + ua['brv'] = e.browser.version; + ua['ss'] = screenSize; + ua['de'] = deviceType; + ua['tz'] = window.Intl.DateTimeFormat().resolvedOptions().timeZone; } var signedToken = getJWToken(ua); payload['visitor_data'] = signedToken; + winPayload['visitor_data'] = signedToken; return signedToken; } -ascAdapter.dataProcess = function(t) { - payload['auction_id'] = t.auctionId; - payload['auction_start'] = t.timestamp; +ascAdapter.dataProcess = function (t) { + if (isBydata) { payload['bydata_debug'] = 'true'; } + _logInfo('fulldata - ', t); + payload['aid'] = t.auctionId; + payload['as'] = t.timestamp; payload['auctionData'] = []; var bidderRequestsData = []; var bidsReceivedData = []; t.bidderRequests && t.bidderRequests.forEach(bidReq => { @@ -228,69 +315,80 @@ ascAdapter.dataProcess = function(t) { bidderRequestsData.push(pObj); }); t.bidsReceived && t.bidsReceived.forEach(bid => { - const {requestId, bidder, width, height, cpm, currency, timeToRespond, adUnitCode} = bid; - bidsReceivedData.push({requestId, bidder, width, height, cpm, currency, timeToRespond, adUnitCode}); + const { requestId, bidder, width, height, cpm, currency, timeToRespond, adUnitCode } = bid; + bidsReceivedData.push({ requestId, bidder, width, height, cpm, currency, timeToRespond, adUnitCode }); }); bidderRequestsData.length > 0 && bidderRequestsData.forEach(bdObj => { var bdsArray = bdObj['bids']; bdsArray.forEach(bid => { - const {adUnitCode, sizes, bidder, bidId, mediaTypes} = bid; + const { adUnitCode, sizes, bidder, bidId, mediaTypes } = bid; sizes.forEach(size => { var sstr = size[0] + 'x' + size[1] - payload['auctionData'].push({adUnit: adUnitCode, size: sstr, media_type: mediaTypes[0], bids_bidder: bidder, bids_bid_id: bidId}); + payload['auctionData'].push({ au: getAdunitName(adUnitCode), auc: adUnitCode, aus: sstr, mt: mediaTypes[0], bidadv: bidder, bid: bidId, inb: 0, ito: 0, ipwb: 0, iwb: 0 }); }); }); }); + bidsReceivedData.length > 0 && bidsReceivedData.forEach(bdRecived => { - const {requestId, bidder, width, height, cpm, currency, timeToRespond} = bdRecived; + const { requestId, bidder, width, height, cpm, currency, timeToRespond } = bdRecived; payload['auctionData'].forEach(rwData => { - if (rwData['bids_bid_id'] === requestId && rwData['size'] === width + 'x' + height) { - rwData['br_request_id'] = requestId; rwData['br_bidder'] = bidder; rwData['br_pb_mg'] = cpm; - rwData['br_currency'] = currency; rwData['br_time_to_respond'] = timeToRespond; rwData['br_size'] = width + 'x' + height; + if (rwData['bid'] === requestId && rwData['aus'] === width + 'x' + height) { + rwData['brid'] = requestId; rwData['bradv'] = bidder; rwData['br_pb_mg'] = cpm; + rwData['cur'] = currency; rwData['br_tr'] = timeToRespond; rwData['brs'] = width + 'x' + height; } }) }); + + var prebidWinningBids = auctionManager.getBidsReceived().filter(bid => bid.status === CONSTANTS.BID_STATUS.BID_TARGETING_SET); + prebidWinningBids && prebidWinningBids.length > 0 && prebidWinningBids.forEach(pbbid => { + payload['auctionData'] && payload['auctionData'].forEach(rwData => { + if (rwData['bid'] === pbbid.requestId && rwData['brs'] === pbbid.size) { + rwData['ipwb'] = 1; + } + }); + }) + + var winningBids = auctionManager.getAllWinningBids(); + winningBids && winningBids.length > 0 && winningBids.forEach(wBid => { + payload['auctionData'] && payload['auctionData'].forEach(rwData => { + if (rwData['bid'] === wBid.requestId && rwData['brs'] === wBid.size) { + rwData['iwb'] = 1; + } + }); + }) + payload['auctionData'] && payload['auctionData'].length > 0 && payload['auctionData'].forEach(u => { bdNbTo['to'].forEach(i => { - if (u.bids_bid_id === i.bidId) u.is_timeout = 1; + if (u.bid === i.bidId) u.ito = 1; }); bdNbTo['nb'].forEach(i => { - if (u.adUnit === i.adUnitCode && u.bids_bidder === i.bidder && u.bids_bid_id === i.bidId) { u.is_nobid = 1; } + if (u.bidadv === i.bidder && u.bid === i.bidId) { u.inb = 1; } }) }); return payload; } -ascAdapter.sendPayload = function () { - var obj = { 'records': [ { 'value': payload } ] }; +ascAdapter.sendPayload = function (data) { + var obj = { 'records': [{ 'value': data }] }; let strJSON = JSON.stringify(obj); - _logInfo(' sendPayload ', JSON.stringify(obj)); - ajax(DEFAULT_EVENT_URL, undefined, strJSON, { + sendDataOnKf(strJSON); +} + +function sendDataOnKf(dataObj) { + ajax(DEFAULT_EVENT_URL, { + success: function () { + _logInfo('send data success'); + }, + error: function (e) { + _logInfo('send data error', e); + } + }, dataObj, { contentType: 'application/vnd.kafka.json.v2+json', method: 'POST', withCredentials: true }); } -function addKeyForPrebidWinningAndWinningsBids() { - var prebidWinningBids = $$PREBID_GLOBAL$$.getAllPrebidWinningBids(); - var winningBids = $$PREBID_GLOBAL$$.getAllWinningBids(); - prebidWinningBids && prebidWinningBids.length > 0 && prebidWinningBids.forEach(pbbid => { - payload['auctionData'] && payload['auctionData'].forEach(rwData => { - if (rwData['bids_bid_id'] === pbbid.requestId && rwData['br_size'] === pbbid.size) { - rwData['is_prebid_winning_bid'] = 1; - } - }); - }) - winningBids && winningBids.length > 0 && winningBids.forEach(wBid => { - payload['auctionData'] && payload['auctionData'].forEach(rwData => { - if (rwData['bids_bid_id'] === wBid.requestId && rwData['br_size'] === wBid.size) { - rwData['is_winning_bid'] = 1; - } - }); - }) -} - adapterManager.registerAnalyticsAdapter({ adapter: ascAdapter, code: 'bydata' @@ -305,7 +403,7 @@ function _logError(message) { } function buildLogMessage(message) { - return 'Bydata Prebid Analytics: ' + message; + return 'Bydata Prebid Analytics ' + versionCode + ':' + message; } export default ascAdapter; diff --git a/modules/byDataAnalyticsAdapter.md b/modules/byDataAnalyticsAdapter.md index 84207d8b3a1..a0780ecb514 100644 --- a/modules/byDataAnalyticsAdapter.md +++ b/modules/byDataAnalyticsAdapter.md @@ -1,7 +1,7 @@ # Overview layout: Analytics Adapter -title: Ascendeum Pvt Ltd. (https://ascendeum.com/) +title: byData. (https://bydata.com/) description: Bydata Analytics Adapter modulecode: byDataAnalyticsAdapter gdpr_supported: false (EU GDPR support) @@ -13,11 +13,12 @@ enable_download: false (in case you don't want users of the website to dow Module Name: Bydata Analytics Adapter Module Type: Analytics Adapter -Maintainer: Ascendeum +Maintainer: byData # Description -Analytics adapter for https://ascendeum.com/. Contact engineering@ascendeum.com for information. +Analytics adapter for https://bydata.com/. Contact admin@byData.com for information. + # Test Parameters @@ -25,10 +26,8 @@ Analytics adapter for https://ascendeum.com/. Contact engineering@ascendeum.com { provider: 'bydata', options : { - clientId: "ASCENDEUM_PROVIDED_CLIENT_ID", + clientId: "please contact byData team to get a clientId", logFrequency : 100, // Sample Rate Default - 1% } } ``` - - diff --git a/modules/byplayBidAdapter.md b/modules/byplayBidAdapter.md deleted file mode 100644 index 67fb9c40d35..00000000000 --- a/modules/byplayBidAdapter.md +++ /dev/null @@ -1,37 +0,0 @@ -# Overview - -``` -Module Name: ByPlay Bidder Adapter -Module Type: Bidder Adapter -Maintainer: byplayers@tsumikiinc.com -``` - -# Description - -Connects to ByPlay exchange for bids. - -ByPlay bid adapter supports Video. - -# Test Parameters -``` - const adUnits = [ - { - code: 'byplay-ad', - mediaTypes: { - video: { - playerSize: [400, 225], - context: 'outstream' - } - }, - bids: [ - { - bidder: 'byplay', - params: { - sectionId: '7986', - env: 'dev' - } - } - ] - } - ]; -``` diff --git a/modules/c1xBidAdapter.js b/modules/c1xBidAdapter.js new file mode 100644 index 00000000000..8c9407825ba --- /dev/null +++ b/modules/c1xBidAdapter.js @@ -0,0 +1,209 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { logInfo, logError } from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'c1x'; +const URL = 'https://hb-stg.c1exchange.com/ht'; +// const PIXEL_ENDPOINT = '//px.c1exchange.com/pubpixel/'; +const LOG_MSG = { + invalidBid: 'C1X: [ERROR] bidder returns an invalid bid', + noSite: 'C1X: [ERROR] no site id supplied', + noBid: 'C1X: [INFO] creating a NO bid for Adunit: ', + bidWin: 'C1X: [INFO] creating a bid for Adunit: ' +}; + +/** + * Adapter for requesting bids from C1X header tag server. + * v3.1 (c) C1X Inc., 2018 + */ + +export const c1xAdapter = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + // check the bids sent to c1x bidder + isBidRequestValid: function (bid) { + if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { + return false; + } + if (typeof bid.params.placementId === 'undefined') { + return false; + } + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let payload = {}; + let tagObj = {}; + let bidRequest = []; + const adunits = validBidRequests.length; + const rnd = new Date().getTime(); + const c1xTags = validBidRequests.map(bidToTag); + const bidIdTags = validBidRequests.map(bidToShortTag); // include only adUnitCode and bidId from request obj + + // flattened tags in a tag object + tagObj = c1xTags.reduce((current, next) => Object.assign(current, next)); + + payload = { + adunits: adunits.toString(), + rnd: rnd.toString(), + response: 'json', + compress: 'gzip' + }; + + // for GDPR support + if (bidderRequest && bidderRequest.gdprConsent) { + payload['consent_string'] = bidderRequest.gdprConsent.consentString; + payload['consent_required'] = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies.toString() : 'true' + ; + } + + Object.assign(payload, tagObj); + let payloadString = stringifyPayload(payload); + // ServerRequest object + bidRequest.push({ + method: 'GET', + url: URL, + data: payloadString, + bids: bidIdTags + }); + return bidRequest; + }, + + interpretResponse: function (serverResponse, requests) { + serverResponse = serverResponse.body; + requests = requests.bids || []; + const currency = 'USD'; + const bidResponses = []; + let netRevenue = false; + + if (!serverResponse || serverResponse.error) { + let errorMessage = serverResponse.error; + logError(LOG_MSG.invalidBid + errorMessage); + return bidResponses; + } else { + serverResponse.forEach(bid => { + logInfo(bid) + if (bid.bid) { + if (bid.bidType === 'NET_BID') { + netRevenue = !netRevenue; + } + const curBid = { + requestId: bid.bidId, + width: bid.width, + height: bid.height, + cpm: bid.cpm, + ad: bid.ad, + creativeId: bid.crid, + currency: currency, + ttl: 300, + netRevenue: netRevenue + }; + + if (bid.dealId) { + curBid['dealId'] = bid.dealId + } + + for (let i = 0; i < requests.length; i++) { + if (bid.adId === requests[i].adUnitCode) { + curBid.requestId = requests[i].bidId; + } + } + logInfo(LOG_MSG.bidWin + bid.adId + ' size: ' + curBid.width + 'x' + curBid.height); + bidResponses.push(curBid); + } else { + // no bid + logInfo(LOG_MSG.noBid + bid.adId); + } + }); + } + + return bidResponses; + } + +} + +function bidToTag(bid, index) { + const tag = {}; + const adIndex = 'a' + (index + 1).toString(); // ad unit id for c1x + const sizeKey = adIndex + 's'; + const priceKey = adIndex + 'p'; + const dealKey = adIndex + 'd'; + // TODO: Multiple Floor Prices + + const sizesArr = bid.sizes; + const floorPriceMap = getBidFloor(bid); + + const dealId = bid.params.dealId || ''; + + if (dealId) { + tag[dealKey] = dealId; + } + + tag[adIndex] = bid.adUnitCode; + tag[sizeKey] = sizesArr.reduce((prev, current) => prev + (prev === '' ? '' : ',') + current.join('x'), ''); + const newSizeArr = tag[sizeKey].split(','); + if (floorPriceMap) { + newSizeArr.forEach(size => { + if (size in floorPriceMap) { + tag[priceKey] = floorPriceMap[size].toString(); + } // we only accept one cpm price in floorPriceMap + }); + } + if (bid.params.pageurl) { + tag['pageurl'] = bid.params.pageurl; + } + + return tag; +} + +function getBidFloor(bidRequest) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'banner', + size: '*', + }); + } + + let floor = + floorInfo.floor || + bidRequest.params.bidfloor || + bidRequest.params.floorPriceMap || + 0; + + return floor; +} + +function bidToShortTag(bid) { + const tag = {}; + tag.adUnitCode = bid.adUnitCode; + tag.bidId = bid.bidId; + return tag; +} + +function stringifyPayload(payload) { + let payloadString = []; + for (var key in payload) { + if (payload.hasOwnProperty(key)) { + payloadString.push(key + '=' + payload[key]); + } + } + return payloadString.join('&'); +} + +registerBidder(c1xAdapter); diff --git a/modules/c1xBidAdapter.md b/modules/c1xBidAdapter.md index 83a4ff1ea81..9a7cef486d2 100644 --- a/modules/c1xBidAdapter.md +++ b/modules/c1xBidAdapter.md @@ -2,7 +2,7 @@ Module Name: C1X Bidder Adapter Module Type: Bidder Adapter -Maintainer: cathy@c1exchange.com +Maintainer: vishnu@c1exchange.com # Description @@ -10,23 +10,61 @@ Module that connects to C1X's demand sources # Test Parameters ``` - var adUnits = [ - { - code: 'test-div', - sizes: [[300, 600], [300, 250]], - bids: [ - { - bidder: 'c1x', - params: { - siteId: '9999', - pixelId: '12345', - floorPriceMap: { - '300x250': 0.20, - '300x600': 0.30 - }, //optional - } - } - ] - }, - ]; + var adUnits = [{ + code: 'test-div-1', + mediaTypes: { + banner: { + sizes: [[750, 200]], + } + }, + bids: [{ + bidder: 'c1x', + params: { + placementId: 'div-gpt-ad-1654594619717-0', + 'floorPriceMap': { + '300x250': 4.35 + } + } + }] + }, { + code: 'test-div-2', + mediaTypes: { + banner: { + sizes: [[300, 250], [750, 200]], + } + }, + bids: [{ + bidder: 'c1x', + params: { + placementId: 'div-gpt-ad-1654940683355-0', + 'floorPriceMap': { + '300x250': 4.35 + } + dealId: '1233' // optional parameter + } + }] + }]; + + + pbjs.bidderSettings = { + c1x: { + siteId: 999, + adserverTargeting: [{ + key: "hb_bidder", + val: function(bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function(bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function(bidResponse) { + return bidResponse.pbLg; + } + }] + } + } ``` \ No newline at end of file diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index 65d1ced30e2..7c6b0411023 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -1,7 +1,6 @@ -import { deepAccess, isArray, _each, logWarn, isEmpty } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js' -import { config } from '../src/config.js' -import { getStorageManager } from '../src/storageManager.js'; +import {_each, deepAccess, isArray, isEmpty, logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'ccx' const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -20,7 +19,8 @@ function _getDeviceObj () { function _getSiteObj (bidderRequest) { let site = {} - let url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + // TODO: does the fallback to window.location make sense? + let url = bidderRequest?.refererInfo?.page || window.location.href if (url.length > 0) { url = url.split('?')[0] } diff --git a/modules/cedatoBidAdapter.md b/modules/cedatoBidAdapter.md deleted file mode 100644 index 088f8a4baef..00000000000 --- a/modules/cedatoBidAdapter.md +++ /dev/null @@ -1,53 +0,0 @@ -# Overview - -``` -Module Name: Cedato Bidder Adapter -Module Type: Bidder Adapter -Maintainer: alexk@cedato.com -``` - -# Description - -Connects to Cedato Bidder. -Player ID must be replaced. You can approach your Cedato account manager to get one. - -# Test Parameters -``` -var adUnits = [ - // Banner - { - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - // You can choose one of them - sizes: [ - [300, 250], - [300, 600], - [240, 400], - [728, 90], - ] - } - }, - bids: [ - { - bidder: "cedato", - params: { - player_id: 1450133326, - } - } - ] - } -]; - -pbjs.que.push(() => { - pbjs.setConfig({ - userSync: { - syncEnabled: true, - enabledBidders: ['cedato'], - pixelEnabled: true, - syncsPerBidder: 200, - syncDelay: 100, - }, - }); -}); -``` diff --git a/modules/cleanioRtdProvider.js b/modules/cleanioRtdProvider.js index b9fdcef768e..a6ac3fc1317 100644 --- a/modules/cleanioRtdProvider.js +++ b/modules/cleanioRtdProvider.js @@ -8,6 +8,8 @@ import { submodule } from '../src/hook.js'; import { logError, generateUUID, insertElement } from '../src/utils.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; // ============================ MODULE STATE =============================== @@ -147,6 +149,26 @@ function readConfig(config) { } } +/** + * The function to be called upon module init + * Defined as a variable to be able to reset it naturally + */ +let startBillableEvents = function() { + // Upon clean.io submodule initialization, every winner bid is considered to be protected + // and therefore, subjected to billing + events.on(CONSTANTS.EVENTS.BID_WON, winnerBidResponse => { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + vendor: 'clean.io', + billingId: generateUUID(), + type: 'impression', + // TODO: if absolutely crucial, winnerBidResponse may be used + // to track down auctionId and transactionId + // However, those seem to be of importance for Demand Managers, + // while these billable events are for publishers + }); + }); +} + // ============================ MODULE REGISTRATION =============================== /** @@ -160,6 +182,13 @@ function beforeInit() { try { readConfig(config); onModuleInit(); + + // Subscribing once to ensure no duplicate events + // in case module initialization code runs multiple times + // This should have been a part of submodule definition, but well... + // The assumption here is that in production init() will be called exactly once + startBillableEvents(); + startBillableEvents = () => {}; return true; } catch (err) { if (err instanceof ConfigError) { diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index 3fda9917715..ba72eec3d95 100644 --- a/modules/cleanmedianetBidAdapter.js +++ b/modules/cleanmedianetBidAdapter.js @@ -1,6 +1,5 @@ import {deepAccess, getDNT, inIframe, isArray, isNumber, logError, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; @@ -64,15 +63,14 @@ export const spec = { params.supplyPartnerId }/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + (params.query ? '&' + params.query : ''); - let url = - config.getConfig('pageUrl') || bidderRequest.refererInfo.referer; + let url = bidderRequest.refererInfo.page; const rtbBidRequest = { id: auctionId, site: { - domain: helper.getTopWindowDomain(url), + domain: bidderRequest.refererInfo.domain, page: url, - ref: bidderRequest.refererInfo.referer + ref: bidderRequest.refererInfo.ref }, device: { ua: navigator.userAgent, @@ -106,12 +104,12 @@ export const spec = { ext: { consent: bidderRequest.gdprConsent.consentString } - } + }; } const imp = { id: transactionId, - instl: params.instl === 1 ? 1 : 0, + instl: deepAccess(bidRequest.ortb2Imp, 'instl') === 1 || params.instl === 1 ? 1 : 0, tagid: adUnitCode, bidfloor: 0, bidfloorcur: 'USD', diff --git a/modules/clickforceBidAdapter.js b/modules/clickforceBidAdapter.js index eceb4934b04..92bc9b1bad2 100644 --- a/modules/clickforceBidAdapter.js +++ b/modules/clickforceBidAdapter.js @@ -1,6 +1,7 @@ import { _each } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'clickforce'; const ENDPOINT_URL = 'https://ad.holmesmind.com/adserver/prebid.json?cb=' + new Date().getTime() + '&hb=1&ver=1.21'; @@ -24,6 +25,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const bidParams = []; _each(validBidRequests, function(bid) { bidParams.push({ diff --git a/modules/clicktripzBidAdapter.md b/modules/clicktripzBidAdapter.md deleted file mode 100644 index 1de1e26f37a..00000000000 --- a/modules/clicktripzBidAdapter.md +++ /dev/null @@ -1,35 +0,0 @@ -# Overview - -``` -Module Name: Clicktripz Bidder Adapter -Module Type: Bidder Adapter -Maintainer: integration-support@clicktripz.com -``` - -# Description -Our module makes it easy to integrate Clicktripz demand sources into your website. - -Supported Ad Fortmats: -* Banner - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [ - { - bidder: "clicktripz", - params: { - placementId: '4312c63f', - siteId: 'prebid', - } - } - ] - } - ]; diff --git a/modules/codefuelBidAdapter.js b/modules/codefuelBidAdapter.js index b9da86ac24e..bde168a79e3 100644 --- a/modules/codefuelBidAdapter.js +++ b/modules/codefuelBidAdapter.js @@ -1,6 +1,7 @@ -import { deepAccess, isArray } from '../src/utils.js'; +import {deepAccess, isArray} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import {BANNER} from '../src/mediaTypes.js'; + const BIDDER_CODE = 'codefuel'; const CURRENCY = 'USD'; @@ -27,8 +28,8 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const page = bidderRequest.refererInfo.referer; - const domain = getDomainFromURL(page) + const page = bidderRequest.refererInfo.page; + const domain = bidderRequest.refererInfo.domain; const ua = navigator.userAgent; const devicetype = getDeviceType() const publisher = setOnAny(validBidRequests, 'params.publisher'); @@ -128,12 +129,6 @@ export const spec = { } registerBidder(spec); -function getDomainFromURL(url) { - let anchor = document.createElement('a'); - anchor.href = url; - return anchor.hostname; -} - function getDeviceType() { if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { return 5; // 'tablet' diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js index f61d58664ca..ce366cbecc8 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -50,7 +50,8 @@ export const spec = { currency: currency, sizes: sizes, bidId: bidRequest.bidId, - referer: bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + referer: bidderRequest.refererInfo.page, }; return { diff --git a/modules/coinzillaBidAdapter.js b/modules/coinzillaBidAdapter.js index cd087daa8cb..7e9fb964a87 100644 --- a/modules/coinzillaBidAdapter.js +++ b/modules/coinzillaBidAdapter.js @@ -39,7 +39,8 @@ export const spec = { width: width, height: height, bidId: bidRequest.bidId, - referer: bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + referer: bidderRequest.refererInfo.page, }; return { method: 'POST', diff --git a/modules/collectcentBidAdapter.md b/modules/collectcentBidAdapter.md deleted file mode 100644 index 938bdc420cd..00000000000 --- a/modules/collectcentBidAdapter.md +++ /dev/null @@ -1,27 +0,0 @@ -# Overview - -``` -Module Name: Collectcent SSP Bidder Adapter -Module Type: Bidder Adapter -Maintainer: dev.collectcent@gmail.com -``` - -# Description - -Module that connects to Collectcent SSP demand sources - -# Test Parameters -``` - var adUnits = [{ - code: 'placementCode', - sizes: [[300, 250]], - bids: [{ - bidder: 'collectcent', - params: { - placementId: 0, - traffic: 'banner' - } - }] - } - ]; -``` diff --git a/modules/colombiaBidAdapter.md b/modules/colombiaBidAdapter.md deleted file mode 100644 index c754e49771d..00000000000 --- a/modules/colombiaBidAdapter.md +++ /dev/null @@ -1,31 +0,0 @@ -# Overview - -``` -Module Name: COLOMBIA Bidder Adapter -Module Type: Bidder Adapter -Maintainer: colombiaonline@timesinteret.in -``` - -# Description - -Connect to COLOMBIA for bids. - -COLOMBIA adapter requires setup and approval from the COLOMBIA team. Please reach out to your account team or colombiaonline@timesinteret.in for more information. - -# Test Parameters -``` - var adUnits = [{ - code: 'test-ad-div', - mediaTypes: { - banner: { - sizes: [[300, 250],[728,90],[320,50]] - } - }, - bids: [{ - bidder: 'colombia', - params: { - placementId: '307466' - } - }] - }]; -``` diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 8fab37a433f..9a0ff3a5422 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -3,6 +3,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'colossusssp'; const G_URL = 'https://colossusssp.com/?c=o&m=multi'; @@ -61,6 +62,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let deviceWidth = 0; let deviceHeight = 0; let winLocation; @@ -75,7 +79,7 @@ export const spec = { winLocation = window.location; } - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + const refferUrl = bidderRequest.refererInfo?.page; let refferLocation; try { refferLocation = refferUrl && new URL(refferUrl); @@ -83,6 +87,7 @@ export const spec = { logMessage(e); } + // TODO: does the fallback to window.location make sense? const location = refferLocation || winLocation; let placements = []; let request = { @@ -100,19 +105,18 @@ export const spec = { request.ccpa = bidderRequest.uspConsent; } if (bidderRequest.gdprConsent) { - request.gdpr_consent = bidderRequest.gdprConsent.consentString || 'ALL' - request.gdpr_require = bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + request.gdpr_consent = bidderRequest.gdprConsent.consentString || 'ALL'; + request.gdpr_require = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; } } for (let i = 0; i < validBidRequests.length; i++) { let bid = validBidRequests[i]; - let traff = bid.params.traffic || BANNER; + const { mediaTypes } = bid; let placement = { placementId: bid.params.placement_id, groupId: bid.params.group_id, bidId: bid.bidId, - traffic: traff, eids: [], floor: {} }; @@ -133,32 +137,39 @@ export const spec = { rtiPartner: 'TDID' }); } - if (traff === BANNER) { - placement.sizes = bid.mediaTypes[BANNER].sizes - } else if (traff === VIDEO) { - placement.sizes = bid.mediaTypes[VIDEO].playerSize; - placement.playerSize = bid.mediaTypes[VIDEO].playerSize; - placement.minduration = bid.mediaTypes[VIDEO].minduration; - placement.maxduration = bid.mediaTypes[VIDEO].maxduration; - placement.mimes = bid.mediaTypes[VIDEO].mimes; - placement.protocols = bid.mediaTypes[VIDEO].protocols; - placement.startdelay = bid.mediaTypes[VIDEO].startdelay; - placement.placement = bid.mediaTypes[VIDEO].placement; - placement.skip = bid.mediaTypes[VIDEO].skip; - placement.skipafter = bid.mediaTypes[VIDEO].skipafter; - placement.minbitrate = bid.mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = bid.mediaTypes[VIDEO].maxbitrate; - placement.delivery = bid.mediaTypes[VIDEO].delivery; - placement.playbackmethod = bid.mediaTypes[VIDEO].playbackmethod; - placement.api = bid.mediaTypes[VIDEO].api; - placement.linearity = bid.mediaTypes[VIDEO].linearity; + + if (mediaTypes && mediaTypes[BANNER]) { + placement.traffic = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.traffic = VIDEO; + placement.sizes = mediaTypes[VIDEO].playerSize; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.traffic = NATIVE; + placement.native = mediaTypes[NATIVE]; } + if (typeof bid.getFloor === 'function') { let tmpFloor = {}; for (let size of placement.sizes) { tmpFloor = bid.getFloor({ currency: 'USD', - mediaType: traff, + mediaType: placement.traffic, size: size }); if (tmpFloor) { diff --git a/modules/colossussspBidAdapter.md b/modules/colossussspBidAdapter.md index 4187dfbf36e..45af89580c1 100644 --- a/modules/colossussspBidAdapter.md +++ b/modules/colossussspBidAdapter.md @@ -22,22 +22,45 @@ Module that connects to Colossus SSP demand sources bids: [{ bidder: 'colossusssp', params: { - placement_id: 0, - traffic: 'banner' + placement_id: 0 } }] }, { code: 'placementid_1', mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, } }, bids: [{ bidder: 'colossusssp', params: { - group_id: 0, - traffic: 'banner' + group_id: 0 + } + }] + }, { + code: 'placementid_2', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [{ + bidder: 'colossusssp', + params: { + placement_id: 0, } }] }]; diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js index 77f918276bc..a89c4c617b6 100644 --- a/modules/compassBidAdapter.js +++ b/modules/compassBidAdapter.js @@ -1,4 +1,5 @@ import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -112,6 +113,9 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let deviceWidth = 0; let deviceHeight = 0; @@ -126,14 +130,14 @@ export const spec = { winLocation = window.location; } - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; let refferLocation; try { refferLocation = refferUrl && new URL(refferUrl); } catch (e) { logMessage(e); } - + // TODO: does the fallback make sense here? let location = refferLocation || winLocation; const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; const host = location.host; diff --git a/modules/concertAnalyticsAdapter.js b/modules/concertAnalyticsAdapter.js index cd52a2ffabf..210d1898338 100644 --- a/modules/concertAnalyticsAdapter.js +++ b/modules/concertAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { logMessage } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 99e2492fb94..fc6fc23c26d 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -1,6 +1,7 @@ import { logWarn, logMessage, debugTurnedOn, generateUUID } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js' +import { getStorageManager } from '../src/storageManager.js'; +import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const BIDDER_CODE = 'concert'; const CONCERT_ENDPOINT = 'https://bids.concert.io'; @@ -36,7 +37,7 @@ export const spec = { let payload = { meta: { prebidVersion: '$prebid.version$', - pageUrl: bidderRequest.refererInfo.referer, + pageUrl: bidderRequest.refererInfo.page, screen: [window.screen.width, window.screen.height].join('x'), debug: debugTurnedOn(), uid: getUid(bidderRequest), @@ -45,7 +46,7 @@ export const spec = { uspConsent: bidderRequest.uspConsent, gdprConsent: bidderRequest.gdprConsent } - } + }; payload.slots = validBidRequests.map(bidRequest => { let slot = { @@ -57,8 +58,9 @@ export const spec = { slotType: bidRequest.params.slotType, adSlot: bidRequest.params.slot || bidRequest.adUnitCode, placementId: bidRequest.params.placementId || '', - site: bidRequest.params.site || bidderRequest.refererInfo.referer - } + site: bidRequest.params.site || bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref + }; return slot; }); @@ -69,7 +71,7 @@ export const spec = { method: 'POST', url: `${CONCERT_ENDPOINT}/bids/prebid`, data: JSON.stringify(payload) - } + }; }, /** * Unpack the response from the server into a list of bids. @@ -101,7 +103,7 @@ export const spec = { creativeId: bid.creativeId, netRevenue: bid.netRevenue, currency: bid.currency - } + }; }); if (debugTurnedOn() && serverBody.debug) { @@ -122,7 +124,7 @@ export const spec = { * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncs = [] + const syncs = []; if (syncOptions.iframeEnabled && !hasOptedOutOfPersonalization()) { let params = []; @@ -203,9 +205,14 @@ function hasOptedOutOfPersonalization() { * @param {BidderRequest} bidderRequest Object which contains any data consent signals */ function consentAllowsPpid(bidderRequest) { - /* NOTE: We cannot easily test GDPR consent, without the + /* NOTE: We can't easily test GDPR consent, without the * `consent-string` npm module; so will have to rely on that * happening on the bid-server. */ - return !(bidderRequest.uspConsent === 'string' && - bidderRequest.uspConsent.toUpperCase().substring(0, 2) === '1YY') + const uspConsent = !(bidderRequest?.uspConsent === 'string' && + bidderRequest?.uspConsent[0] === '1' && + bidderRequest?.uspConsent[2].toUpperCase() === 'Y'); + + const gdprConsent = bidderRequest?.gdprConsent && hasPurpose1Consent(bidderRequest?.gdprConsent); + + return (uspConsent || gdprConsent); } diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index 711afd98d0f..5185308eab0 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -35,9 +35,11 @@ export const spec = { placements: [], time: Date.now(), user: {}, - url: (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : window.location.href, - referrer: window.document.referrer, - referrer_info: bidderRequest.refererInfo, + // TODO: does the fallback to window.location make sense? + url: bidderRequest.refererInfo?.page || window.location.href, + referrer: bidderRequest.refererInfo?.ref, + // TODO: please do not send internal data structures over the network + referrer_info: bidderRequest.refererInfo?.legacy, screensize: getScreenSize(), dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, language: navigator.language, diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 5fbcc0f8ac1..44a0d7d65c6 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -4,27 +4,23 @@ * and make it available for any GDPR supported adapters to read/pass this information to * their system. */ -import {getAdUnitSizes, isFn, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; +import {isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import {gdprDataHandler} from '../src/adapterManager.js'; import {includes} from '../src/polyfill.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; -const DEFAULT_ALLOW_AUCTION_WO_CONSENT = true; +const CMP_VERSION = 2; -export const allowAuction = { - value: DEFAULT_ALLOW_AUCTION_WO_CONSENT, - definedInConfig: false -} export let userCMP; export let consentTimeout; export let gdprScope; export let staticConsentData; -let cmpVersion = 0; let consentData; let addedConsentHook = false; +let provisionalConsent; // add new CMPs here, with their dedicated lookup function const cmpCallMap = { @@ -46,24 +42,16 @@ function lookupStaticConsentData({onSuccess, onError}) { * based on the appropriate result. * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) - * @param width - * @param height size info passed to the SafeFrame API (used only for TCFv1 when Prebid is running within a safeframe) */ -function lookupIabConsent({onSuccess, onError, width, height}) { +function lookupIabConsent({onSuccess, onError}) { function findCMP() { let f = window; let cmpFrame; let cmpFunction; - while (!cmpFrame) { + while (true) { try { - if (typeof f.__tcfapi === 'function' || typeof f.__cmp === 'function') { - if (typeof f.__tcfapi === 'function') { - cmpVersion = 2; - cmpFunction = f.__tcfapi; - } else { - cmpVersion = 1; - cmpFunction = f.__cmp; - } + if (typeof f.__tcfapi === 'function') { + cmpFunction = f.__tcfapi; cmpFrame = f; break; } @@ -72,15 +60,6 @@ function lookupIabConsent({onSuccess, onError, width, height}) { // need separate try/catch blocks due to the exception errors thrown when trying to check for a frame that doesn't exist in 3rd party env try { if (f.frames['__tcfapiLocator']) { - cmpVersion = 2; - cmpFrame = f; - break; - } - } catch (e) { } - - try { - if (f.frames['__cmpLocator']) { - cmpVersion = 1; cmpFrame = f; break; } @@ -95,42 +74,21 @@ function lookupIabConsent({onSuccess, onError, width, height}) { }; } - function v2CmpResponseCallback(tcfData, success) { + function cmpResponseCallback(tcfData, success) { logInfo('Received a response from CMP', tcfData); if (success) { if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') { processCmpData(tcfData, {onSuccess, onError}); + } else { + provisionalConsent = tcfData; } } else { onError('CMP unable to register callback function. Please check CMP setup.'); } } - function handleV1CmpResponseCallbacks() { - const cmpResponse = {}; - - function afterEach() { - if (cmpResponse.getConsentData && cmpResponse.getVendorConsents) { - logInfo('Received all requested responses from CMP', cmpResponse); - processCmpData(cmpResponse, {onSuccess, onError}); - } - } - - return { - consentDataCallback: function (consentResponse) { - cmpResponse.getConsentData = consentResponse; - afterEach(); - }, - vendorConsentsCallback: function (consentResponse) { - cmpResponse.getVendorConsents = consentResponse; - afterEach(); - } - } - } - - let v1CallbackHandler = handleV1CmpResponseCallbacks(); - let cmpCallbacks = {}; - let { cmpFrame, cmpFunction } = findCMP(); + const cmpCallbacks = {}; + const { cmpFrame, cmpFunction } = findCMP(); if (!cmpFrame) { return onError('CMP not found.'); @@ -145,102 +103,49 @@ function lookupIabConsent({onSuccess, onError, width, height}) { // else assume prebid may be inside an iframe and use the IAB CMP locator code to see if CMP's located in a higher parent window. this works in cross domain iframes // if the CMP is not found, the iframe function will call the cmpError exit callback to abort the rest of the CMP workflow - if (isFn(cmpFunction)) { + if (typeof cmpFunction === 'function') { logInfo('Detected CMP API is directly accessible, calling it now...'); - if (cmpVersion === 1) { - cmpFunction('getConsentData', null, v1CallbackHandler.consentDataCallback); - cmpFunction('getVendorConsents', null, v1CallbackHandler.vendorConsentsCallback); - } else if (cmpVersion === 2) { - cmpFunction('addEventListener', cmpVersion, v2CmpResponseCallback); - } - } else if (cmpVersion === 1 && inASafeFrame() && typeof window.$sf.ext.cmp === 'function') { - // this safeframe workflow is only supported with TCF v1 spec; the v2 recommends to use the iframe postMessage route instead (even if you are in a safeframe). - logInfo('Detected Prebid.js is encased in a SafeFrame and CMP is registered, calling it now...'); - callCmpWhileInSafeFrame('getConsentData', v1CallbackHandler.consentDataCallback); - callCmpWhileInSafeFrame('getVendorConsents', v1CallbackHandler.vendorConsentsCallback); + cmpFunction('addEventListener', CMP_VERSION, cmpResponseCallback); } else { logInfo('Detected CMP is outside the current iframe where Prebid.js is located, calling it now...'); - if (cmpVersion === 1) { - callCmpWhileInIframe('getConsentData', cmpFrame, v1CallbackHandler.consentDataCallback); - callCmpWhileInIframe('getVendorConsents', cmpFrame, v1CallbackHandler.vendorConsentsCallback); - } else if (cmpVersion === 2) { - callCmpWhileInIframe('addEventListener', cmpFrame, v2CmpResponseCallback); - } - } - - function inASafeFrame() { - return !!(window.$sf && window.$sf.ext); - } - - function callCmpWhileInSafeFrame(commandName, callback) { - function sfCallback(msgName, data) { - if (msgName === 'cmpReturn') { - let responseObj = (commandName === 'getConsentData') ? data.vendorConsentData : data.vendorConsents; - callback(responseObj); - } - } - - window.$sf.ext.register(width, height, sfCallback); - window.$sf.ext.cmp(commandName); + callCmpWhileInIframe('addEventListener', cmpFrame, cmpResponseCallback); } function callCmpWhileInIframe(commandName, cmpFrame, moduleCallback) { - let apiName = (cmpVersion === 2) ? '__tcfapi' : '__cmp'; + const apiName = '__tcfapi'; - let callName = `${apiName}Call`; + const callName = `${apiName}Call`; /* Setup up a __cmp function to do the postMessage and stash the callback. This function behaves (from the caller's perspective identicially to the in-frame __cmp call */ - if (cmpVersion === 2) { - window[apiName] = function (cmd, cmpVersion, callback, arg) { - let callId = Math.random() + ''; - let msg = { - [callName]: { - command: cmd, - version: cmpVersion, - parameter: arg, - callId: callId - } - }; - - cmpCallbacks[callId] = callback; - cmpFrame.postMessage(msg, '*'); - } - - /** when we get the return message, call the stashed callback */ - window.addEventListener('message', readPostMessageResponse, false); + window[apiName] = function (cmd, cmpVersion, callback, arg) { + const callId = Math.random() + ''; + const msg = { + [callName]: { + command: cmd, + version: cmpVersion, + parameter: arg, + callId: callId + } + }; - // call CMP - window[apiName](commandName, cmpVersion, moduleCallback); - } else { - window[apiName] = function (cmd, arg, callback) { - let callId = Math.random() + ''; - let msg = { - [callName]: { - command: cmd, - parameter: arg, - callId: callId - } - }; - - cmpCallbacks[callId] = callback; - cmpFrame.postMessage(msg, '*'); - } + cmpCallbacks[callId] = callback; + cmpFrame.postMessage(msg, '*'); + } - /** when we get the return message, call the stashed callback */ - window.addEventListener('message', readPostMessageResponse, false); + /** when we get the return message, call the stashed callback */ + window.addEventListener('message', readPostMessageResponse, false); - // call CMP - window[apiName](commandName, undefined, moduleCallback); - } + // call CMP + window[apiName](commandName, CMP_VERSION, moduleCallback); function readPostMessageResponse(event) { - let cmpDataPkgName = `${apiName}Return`; - let json = (typeof event.data === 'string' && includes(event.data, cmpDataPkgName)) ? JSON.parse(event.data) : event.data; + const cmpDataPkgName = `${apiName}Return`; + const json = (typeof event.data === 'string' && includes(event.data, cmpDataPkgName)) ? JSON.parse(event.data) : event.data; if (json[cmpDataPkgName] && json[cmpDataPkgName].callId) { - let payload = json[cmpDataPkgName]; + const payload = json[cmpDataPkgName]; // TODO - clean up this logic (move listeners?); we have duplicate messages responses because 2 eventlisteners are active from the 2 cmp requests running in parallel - if (typeof cmpCallbacks[payload.callId] !== 'undefined') { + if (cmpCallbacks.hasOwnProperty(payload.callId)) { cmpCallbacks[payload.callId](payload.returnValue, payload.success); } } @@ -253,11 +158,8 @@ function lookupIabConsent({onSuccess, onError, width, height}) { * * @param cb A callback that takes: a boolean that is true if the auction should be canceled; an error message and extra * error arguments that will be undefined if there's no error. - * @param width if we are running in an iframe, the TCFv1 spec requires us to use the SafeFrame API to find the CMP - which - * in turn requires width and height. - * @param height see width above */ -function loadConsentData(cb, width = 1, height = 1) { +function loadConsentData(cb) { let isDone = false; let timer = null; @@ -267,7 +169,7 @@ function loadConsentData(cb, width = 1, height = 1) { } isDone = true; gdprDataHandler.setConsentData(consentData); - if (cb != null) { + if (typeof cb === 'function') { cb(shouldCancelAuction, errMsg, ...extraArgs); } } @@ -280,38 +182,43 @@ function loadConsentData(cb, width = 1, height = 1) { const callbacks = { onSuccess: (data) => done(data, false), onError: function (msg, ...extraArgs) { - let consentData = null; - let shouldCancelAuction = true; - if (allowAuction.value && cmpVersion === 1) { - // still set the consentData to undefined when there is a problem as per config options - consentData = storeConsentData(undefined); - shouldCancelAuction = false; - } - done(consentData, shouldCancelAuction, msg, ...extraArgs); + done(null, true, msg, ...extraArgs); } } - cmpCallMap[userCMP]({ - width, - height, - ...callbacks - }); + cmpCallMap[userCMP](callbacks); if (!isDone) { + const onTimeout = () => { + const continueToAuction = (data) => { + done(data, false, 'CMP did not load, continuing auction...'); + } + processCmpData(provisionalConsent, { + onSuccess: continueToAuction, + onError: () => continueToAuction(storeConsentData(undefined)) + }) + } if (consentTimeout === 0) { - processCmpData(undefined, callbacks); + onTimeout(); } else { - timer = setTimeout(function () { - if (cmpVersion === 2) { - // for TCFv2, we allow the auction to continue on timeout - done(storeConsentData(undefined), false, `No response from CMP, continuing auction...`) - } else { - callbacks.onError('CMP workflow exceeded timeout threshold.'); - } - }, consentTimeout); + timer = setTimeout(onTimeout, consentTimeout); } } } +/** + * Like `loadConsentData`, but cache and re-use previously loaded data. + * @param cb + */ +function loadIfMissing(cb) { + if (consentData) { + logInfo('User consent information already known. Pulling internally stored information...'); + // eslint-disable-next-line standard/no-callback-literal + cb(false); + } else { + loadConsentData(cb); + } +} + /** * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the * user's encoded consent string from the supported CMP. Once obtained, the module will store this @@ -321,36 +228,10 @@ function loadConsentData(cb, width = 1, height = 1) { * @param {function} fn required; The next function in the chain, used by hook.js */ export function requestBidsHook(fn, reqBidsConfigObj) { - const load = (() => { - if (consentData) { - logInfo('User consent information already known. Pulling internally stored information...'); - return function (cb) { - // eslint-disable-next-line standard/no-callback-literal - cb(false); - } - } else { - // find sizes from adUnits object - let adUnits = reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits; - let width = 1; - let height = 1; - if (Array.isArray(adUnits) && adUnits.length > 0) { - let sizes = getAdUnitSizes(adUnits[0]); - width = sizes?.[0]?.[0] || 1; - height = sizes?.[0]?.[1] || 1; - } - - return function (cb) { - loadConsentData(cb, width, height); - } - } - })(); - - load(function (shouldCancelAuction, errMsg, ...extraArgs) { + loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { if (errMsg) { let log = logWarn; - if (cmpVersion === 1 && !shouldCancelAuction) { - errMsg = `${errMsg} 'allowAuctionWithoutConsent' activated.`; - } else if (shouldCancelAuction) { + if (shouldCancelAuction) { log = logError; errMsg = `${errMsg} Canceling auction as per consentManagement config.`; } @@ -375,49 +256,25 @@ export function requestBidsHook(fn, reqBidsConfigObj) { * If it's good, then we store the value and call `onSuccess` */ function processCmpData(consentObject, {onSuccess, onError}) { - function checkV1Data(consentObject) { - let gdprApplies = consentObject && consentObject.getConsentData && consentObject.getConsentData.gdprApplies; - return !!( - (typeof gdprApplies !== 'boolean') || - (gdprApplies === true && - !(isStr(consentObject.getConsentData.consentData) && - isPlainObject(consentObject.getVendorConsents) && - Object.keys(consentObject.getVendorConsents).length > 1 - ) - ) - ); - } - - function checkV2Data() { + function checkData() { // if CMP does not respond with a gdprApplies boolean, use defaultGdprScope (gdprScope) - let gdprApplies = consentObject && typeof consentObject.gdprApplies === 'boolean' ? consentObject.gdprApplies : gdprScope; - let tcString = consentObject && consentObject.tcString; + const gdprApplies = consentObject && typeof consentObject.gdprApplies === 'boolean' ? consentObject.gdprApplies : gdprScope; + const tcString = consentObject && consentObject.tcString; return !!( (typeof gdprApplies !== 'boolean') || - (gdprApplies === true && !isStr(tcString)) + (gdprApplies === true && (!tcString || !isStr(tcString))) ); } // do extra things for static config if (userCMP === 'static') { - cmpVersion = (consentObject.getConsentData) ? 1 : (consentObject.getTCData) ? 2 : 0; - // remove extra layer in static v2 data object so it matches normal v2 CMP object for processing step - if (cmpVersion === 2) { - consentObject = consentObject.getTCData; - } + consentObject = consentObject.getTCData; } - // determine which set of checks to run based on cmpVersion - let checkFn = (cmpVersion === 1) ? checkV1Data : (cmpVersion === 2) ? checkV2Data : null; - - if (isFn(checkFn)) { - if (checkFn(consentObject)) { - onError(`CMP returned unexpected value during lookup process.`, consentObject); - } else { - onSuccess(storeConsentData(consentObject)); - } + if (checkData()) { + onError(`CMP returned unexpected value during lookup process.`, consentObject); } else { - onError('Unable to derive CMP version to process data. Consent object does not conform to TCF v1 or v2 specs.', consentObject); + onSuccess(storeConsentData(consentObject)); } } @@ -426,23 +283,15 @@ function processCmpData(consentObject, {onSuccess, onError}) { * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) */ function storeConsentData(cmpConsentObject) { - if (cmpVersion === 1) { - consentData = { - consentString: (cmpConsentObject) ? cmpConsentObject.getConsentData.consentData : undefined, - vendorData: (cmpConsentObject) ? cmpConsentObject.getVendorConsents : undefined, - gdprApplies: (cmpConsentObject) ? cmpConsentObject.getConsentData.gdprApplies : gdprScope - }; - } else { - consentData = { - consentString: (cmpConsentObject) ? cmpConsentObject.tcString : undefined, - vendorData: (cmpConsentObject) || undefined, - gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope - }; - if (cmpConsentObject && cmpConsentObject.addtlConsent && isStr(cmpConsentObject.addtlConsent)) { - consentData.addtlConsent = cmpConsentObject.addtlConsent; - }; + consentData = { + consentString: (cmpConsentObject) ? cmpConsentObject.tcString : undefined, + vendorData: (cmpConsentObject) || undefined, + gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope + }; + if (cmpConsentObject && cmpConsentObject.addtlConsent && isStr(cmpConsentObject.addtlConsent)) { + consentData.addtlConsent = cmpConsentObject.addtlConsent; } - consentData.apiVersion = cmpVersion; + consentData.apiVersion = CMP_VERSION; return consentData; } @@ -452,7 +301,7 @@ function storeConsentData(cmpConsentObject) { export function resetConsentData() { consentData = undefined; userCMP = undefined; - cmpVersion = 0; + consentTimeout = undefined; gdprDataHandler.reset(); } @@ -482,11 +331,6 @@ export function setConsentConfig(config) { logInfo(`consentManagement config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); } - if (typeof config.allowAuctionWithoutConsent === 'boolean') { - allowAuction.value = config.allowAuctionWithoutConsent; - allowAuction.definedInConfig = true; - } - // if true, then gdprApplies should be set to true gdprScope = config.defaultGdprScope === true; @@ -506,12 +350,5 @@ export function setConsentConfig(config) { addedConsentHook = true; gdprDataHandler.enable(); loadConsentData(); // immediately look up consent data to make it available without requiring an auction - - // Raise deprecation warning if 'allowAuctionWithoutConsent' is used with TCF 2. - if (allowAuction.definedInConfig && cmpVersion === 2) { - logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`); - } else if (!allowAuction.definedInConfig && cmpVersion === 1) { - logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`); - } } config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index d6bf913b366..3dcb236ac84 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -7,13 +7,14 @@ import { isFn, logInfo, logWarn, isStr, isNumber, isPlainObject, logError } from '../src/utils.js'; import { config } from '../src/config.js'; import { uspDataHandler } from '../src/adapterManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; const DEFAULT_CONSENT_API = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 50; const USPAPI_VERSION = 1; -export let consentAPI; -export let consentTimeout; +export let consentAPI = DEFAULT_CONSENT_API; +export let consentTimeout = DEFAULT_CONSENT_TIMEOUT; export let staticConsentData; let consentData; @@ -43,7 +44,7 @@ function lookupUspConsent({onSuccess, onError}) { let uspapiFrame; let uspapiFunction; - while (!uspapiFrame) { + while (true) { try { if (typeof f.__uspapi === 'function') { uspapiFunction = f.__uspapi; @@ -149,7 +150,7 @@ function lookupUspConsent({onSuccess, onError}) { function readPostMessageResponse(event) { const res = event && event.data && event.data.__uspapiReturn; if (res && res.callId) { - if (typeof uspapiCallbacks[res.callId] !== 'undefined') { + if (uspapiCallbacks.hasOwnProperty(res.callId)) { uspapiCallbacks[res.callId](res.returnValue, res.success); delete uspapiCallbacks[res.callId]; } @@ -254,7 +255,10 @@ function storeUspConsentData(consentObject) { export function resetConsentData() { consentData = undefined; consentAPI = undefined; + consentTimeout = undefined; uspDataHandler.reset(); + getGlobal().requestBids.getHooks({hook: requestBidsHook}).remove(); + addedConsentHook = false; } /** @@ -264,25 +268,21 @@ export function resetConsentData() { export function setConsentConfig(config) { config = config && config.usp; if (!config || typeof config !== 'object') { - logWarn('consentManagement.usp config not defined, exiting usp consent manager'); - return; + logWarn('consentManagement.usp config not defined, using defaults'); } - if (isStr(config.cmpApi)) { + if (config && isStr(config.cmpApi)) { consentAPI = config.cmpApi; } else { consentAPI = DEFAULT_CONSENT_API; logInfo(`consentManagement.usp config did not specify cmpApi. Using system default setting (${DEFAULT_CONSENT_API}).`); } - if (isNumber(config.timeout)) { + if (config && isNumber(config.timeout)) { consentTimeout = config.timeout; } else { consentTimeout = DEFAULT_CONSENT_TIMEOUT; logInfo(`consentManagement.usp config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); } - - logInfo('USPAPI consentManagement module has been activated...'); - if (consentAPI === 'static') { if (isPlainObject(config.consentData) && isPlainObject(config.consentData.getUSPData)) { if (config.consentData.getUSPData.uspString) staticConsentData = { usPrivacy: config.consentData.getUSPData.uspString }; @@ -291,11 +291,17 @@ export function setConsentConfig(config) { logError(`consentManagement config with cmpApi: 'static' did not specify consentData. No consents will be available to adapters.`); } } + enableConsentManagement(true); +} + +function enableConsentManagement(configFromUser = false) { if (!addedConsentHook) { - $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + logInfo(`USPAPI consentManagement module has been activated${configFromUser ? '' : ` using default values (api: '${consentAPI}', timeout: ${consentTimeout}ms)`}`); + getGlobal().requestBids.before(requestBidsHook, 50); } addedConsentHook = true; uspDataHandler.enable(); loadConsentData(); // immediately look up consent data to make it available without requiring an auction } config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); +setTimeout(() => !addedConsentHook && enableConsentManagement()); diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index de08fc8677a..c91f1a7f906 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -1,15 +1,18 @@ -import { logWarn, createTrackPixelHtml } from '../src/utils.js'; +import { logWarn, deepAccess, isArray, deepSetValue, isFn, isPlainObject } from '../src/utils.js'; +import {config} from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'consumable'; -const BASE_URI = 'https://e.serverbid.com/api/v2' +const BASE_URI = 'https://e.serverbid.com/api/v2'; let siteId = 0; let bidder = 'consumable'; export const spec = { code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -47,8 +50,8 @@ export const spec = { const data = Object.assign({ placements: [], time: Date.now(), - url: bidderRequest.refererInfo.referer, - referrer: document.referrer, + url: bidderRequest.refererInfo.page, + referrer: bidderRequest.refererInfo.ref, source: [{ 'name': 'prebidjs', 'version': '$prebid.version$' @@ -66,6 +69,14 @@ export const spec = { data.ccpa = bidderRequest.uspConsent; } + if (bidderRequest && bidderRequest.schain) { + data.schain = bidderRequest.schain; + } + + if (config.getConfig('coppa')) { + data.coppa = true; + } + validBidRequests.map(bid => { const sizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes || []; const placement = Object.assign({ @@ -73,11 +84,19 @@ export const spec = { adTypes: bid.adTypes || getSize(sizes) }, bid.params); + placement.bidfloor = getBidFloor(bid, sizes); + + if (bid.mediaTypes.video && bid.mediaTypes.video.playerSize) { + placement.video = bid.mediaTypes.video; + } + if (placement.networkId && placement.siteId && placement.unitId && placement.unitName) { data.placements.push(placement); } }); + handleEids(data, validBidRequests); + ret.data = JSON.stringify(data); ret.bidRequest = validBidRequests; ret.bidderRequest = bidderRequest; @@ -123,7 +142,7 @@ export const spec = { bid.creativeId = decision.adId; bid.ttl = 30; bid.netRevenue = true; - bid.referrer = bidRequest.bidderRequest.refererInfo.referer; + bid.referrer = bidRequest.bidderRequest.refererInfo.page; bid.meta = { advertiserDomains: decision.adomain || [] @@ -144,6 +163,13 @@ export const spec = { if (decision.mediaType) { bid.meta.mediaType = decision.mediaType; + + if (decision.mediaType === 'video') { + bid.mediaType = 'video'; + bid.vastUrl = decision.vastUrl || undefined; + bid.vastXml = decision.vastXml || undefined; + bid.videoCacheKey = decision.uuid || undefined; + } } bidResponses.push(bid); @@ -229,9 +255,43 @@ function getSize(sizes) { } function retrieveAd(decision, unitId, unitName) { - let ad = decision.contents && decision.contents[0] && decision.contents[0].body + createTrackPixelHtml(decision.impressionUrl); - + let ad; + if (decision.contents && decision.contents[0]) { + ad = decision.contents[0].body; + } + if (decision.vastXml) { + ad = decision.vastXml; + } return ad; } +function handleEids(data, validBidRequests) { + let bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids'); + if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + deepSetValue(data, 'user.eids', bidUserIdAsEids); + } else { + deepSetValue(data, 'user.eids', undefined); + } +} + +function getBidFloor(bid, sizes) { + if (!isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor; + + let floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: bid.mediaTypes.video ? 'video' : 'banner', + size: sizes.length === 1 ? sizes[0] : '*' + }); + + if (isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + + return floor; +} + registerBidder(spec); diff --git a/modules/contentexchangeBidAdapter.js b/modules/contentexchangeBidAdapter.js index b3a5056f816..becc21555e3 100644 --- a/modules/contentexchangeBidAdapter.js +++ b/modules/contentexchangeBidAdapter.js @@ -2,6 +2,7 @@ import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'contentexchange'; const AD_URL = 'https://eu2.adnetwork.agency/pbjs'; @@ -113,6 +114,9 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let deviceWidth = 0; let deviceHeight = 0; @@ -127,7 +131,7 @@ export const spec = { winLocation = window.location; } - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; let refferLocation; try { refferLocation = refferUrl && new URL(refferUrl); @@ -135,6 +139,7 @@ export const spec = { logMessage(e); } + // TODO: does the fallback to 'window.location' make sense? let location = refferLocation || winLocation; const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; const host = location.host; diff --git a/modules/contentigniteBidAdapter.md b/modules/contentigniteBidAdapter.md deleted file mode 100644 index 1f3a543b621..00000000000 --- a/modules/contentigniteBidAdapter.md +++ /dev/null @@ -1,30 +0,0 @@ -# Overview - -``` -Module Name: Content Ignite Bidder Adapter -Module Type: Bidder Adapter -Maintainer: jamie@contentignite.com -``` - -# Description - -Module that connects to Content Ignites bidder application. - -# Test Parameters - -``` - var adUnits = [{ - code: 'display-div', - sizes: [[728, 90]], // a display size - bids: [{ - bidder: "contentignite", - params: { - accountID: '168237', - zoneID: '299680', - keyword: 'business', //optional - minCPM: '0.10', //optional - maxCPM: '1.00' //optional - } - }] - }]; -``` diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index a6b631372e0..41a8200c6ea 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -2,7 +2,6 @@ import { logWarn, isStr, deepAccess, isArray, getBidIdParameter, deepSetValue, i import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import { config } from '../src/config.js'; const GVLID = 24; @@ -55,7 +54,7 @@ export const spec = { * @return {ServerRequest} Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { - const page = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.referer : ''; + const page = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.page : ''; let siteId = ''; let requestId = ''; let pubcid = null; @@ -177,7 +176,7 @@ export const spec = { payload.user = {ext: userExt}; } - const firstPartyData = config.getConfig('ortb2') || {}; + const firstPartyData = bidderRequest.ortb2 || {}; mergeDeep(payload, firstPartyData); return { diff --git a/modules/cosmosBidAdapter.md b/modules/cosmosBidAdapter.md deleted file mode 100644 index 187a19ba17a..00000000000 --- a/modules/cosmosBidAdapter.md +++ /dev/null @@ -1,80 +0,0 @@ -# Overview - -``` -Module Name: Cosmos Bid Adapter -Module Type: Bidder Adapter -Maintainer: dev@cosmoshq.com -``` - -# Description - -Module that connects to Cosmos server for bids. -Supported Ad Fortmats: -* Banner -* Video - -# Configuration -## Following configuration required for enabling user sync. -```javascript -pbjs.setConfig({ - userSync: { - iframeEnabled: true, - enabledBidders: ['cosmos'], - syncDelay: 6000 - }}); -``` -## For Video ads, enable prebid cache -```javascript -pbjs.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache' - } -}); -``` - -# Test Parameters -``` - var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - mediaTypes: { - banner: { //supported as per the openRTB spec - sizes: [[300, 250]] // required - } - }, - bids: [ - { - bidder: "cosmos", - params: { - publisherId: 1001, // required - tagId: 1 // optional - } - } - ] - }, - // Video adUnit - { - code: 'video-div', - mediaTypes: { - video: { // supported as per the openRTB spec - sizes: [[300, 50]], // required - mimes : ['video/mp4', 'application/javascript'], // required - context: 'instream' // optional - } - }, - bids: [ - { - bidder: "cosmos", - params: { - publisherId: 1001, // required - tagId: 1, // optional - video: { // supported as per the openRTB spec - - } - } - } - ] - } - ]; -``` diff --git a/modules/cpexIdSystem.js b/modules/cpexIdSystem.js index f3ddc85ed31..5c2e6bc212d 100644 --- a/modules/cpexIdSystem.js +++ b/modules/cpexIdSystem.js @@ -12,11 +12,11 @@ import { getStorageManager } from '../src/storageManager.js' export const storage = getStorageManager({ gvlid: 570, moduleName: 'cpexId' }) // Returns the id string from either cookie or localstorage -const getId = () => { return storage.getCookie('caid') || storage.getDataFromLocalStorage('caid') } +const readId = () => { return storage.getCookie('czaid') || storage.getDataFromLocalStorage('czaid') } /** @type {Submodule} */ export const cpexIdSubmodule = { - version: '0.0.4', + version: '0.0.5', /** * used to link submodule with config * @type {string} @@ -30,19 +30,18 @@ export const cpexIdSubmodule = { /** * decode the stored id value for passing to bid requests * @function decode - * @param {(Object|string)} value * @returns {(Object|undefined)} */ - decode (value) { return { cpexId: getId() } }, + decode () { return { cpexId: readId() } }, /** * performs action to obtain id and return a value in the callback's response argument * @function - * @param {SubmoduleConfig} [config] - * @param {ConsentData} [consentData] - * @param {(Object|undefined)} cacheIdObj * @returns {IdResponse|undefined} */ - getId (config, consentData) { return { cpexId: getId() } } + getId () { + const id = readId() + return id ? { id: id } : undefined + } } submodule('userId', cpexIdSubmodule) diff --git a/modules/cpmstarBidAdapter.js b/modules/cpmstarBidAdapter.js index 75a7007ee36..64fe85fdbbc 100755 --- a/modules/cpmstarBidAdapter.js +++ b/modules/cpmstarBidAdapter.js @@ -1,4 +1,5 @@ -import { deepAccess, getBidIdParameter, logWarn, logError } from '../src/utils.js'; + +import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -25,11 +26,11 @@ export const spec = { getMediaType: function (bidRequest) { if (bidRequest == null) return BANNER; - return !deepAccess(bidRequest, 'mediaTypes.video') ? BANNER : VIDEO; + return !utils.deepAccess(bidRequest, 'mediaTypes.video') ? BANNER : VIDEO; }, getPlayerSize: function (bidRequest) { - var playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + var playerSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize'); if (playerSize == null) return [640, 440]; if (playerSize[0] != null) playerSize = playerSize[0]; if (playerSize == null || playerSize[0] == null || playerSize[1] == null) return [640, 440]; @@ -47,13 +48,13 @@ export const spec = { for (var i = 0; i < validBidRequests.length; i++) { var bidRequest = validBidRequests[i]; var referer = encodeURIComponent(bidderRequest.refererInfo.referer); - var e = getBidIdParameter('endpoint', bidRequest.params); + var e = utils.getBidIdParameter('endpoint', bidRequest.params); var ENDPOINT = e == 'dev' ? ENDPOINT_DEV : e == 'staging' ? ENDPOINT_STAGING : ENDPOINT_PRODUCTION; var mediaType = spec.getMediaType(bidRequest); var playerSize = spec.getPlayerSize(bidRequest); var videoArgs = '&fv=0' + (playerSize ? ('&w=' + playerSize[0] + '&h=' + playerSize[1]) : ''); var url = ENDPOINT + '?media=' + mediaType + (mediaType == VIDEO ? videoArgs : '') + - '&json=c_b&mv=1&poolid=' + getBidIdParameter('placementId', bidRequest.params) + + '&json=c_b&mv=1&poolid=' + utils.getBidIdParameter('placementId', bidRequest.params) + '&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) + '&requestid=' + bidRequest.bidId + '&referer=' + encodeURIComponent(referer); @@ -72,7 +73,7 @@ export const spec = { fixedEncodeURIComponent(node.name || '') + ',' + fixedEncodeURIComponent(node.domain || ''); } - url += '&schain=' + schainString + url += '&schain=' + schainString; } if (bidderRequest.gdprConsent) { @@ -92,10 +93,20 @@ export const spec = { url += '&tfcd=' + (config.getConfig('coppa') ? 1 : 0); } + let body = {}; + let adUnitCode = bidRequest.adUnitCode; + if (adUnitCode) { + body.adUnitCode = adUnitCode; + } + if (mediaType == VIDEO) { + body.video = utils.deepAccess(bidRequest, 'mediaTypes.video'); + } + requests.push({ - method: 'GET', + method: 'POST', url: url, bidRequest: bidRequest, + data: body }); } @@ -116,13 +127,13 @@ export const spec = { var raw = serverResponse.body[i]; var rawBid = raw.creatives[0]; if (!rawBid) { - logWarn('cpmstarBidAdapter: server response failed check'); + utils.logWarn('cpmstarBidAdapter: server response failed check'); return; } var cpm = (parseFloat(rawBid.cpm) || 0); if (!cpm) { - logWarn('cpmstarBidAdapter: server response failed check. Missing cpm') + utils.logWarn('cpmstarBidAdapter: server response failed check. Missing cpm'); return; } @@ -141,7 +152,7 @@ export const spec = { }; if (rawBid.hasOwnProperty('dealId')) { - bidResponse.dealId = rawBid.dealId + bidResponse.dealId = rawBid.dealId; } if (mediaType == BANNER && rawBid.code) { @@ -155,7 +166,7 @@ export const spec = { bidResponse.mediaType = VIDEO; bidResponse.vastXml = rawBid.creativemacros.HTML5VID_VASTSTRING; } else { - return logError('bad response', rawBid); + return utils.logError('bad response', rawBid); } bidResponses.push(bidResponse); diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 61ca4f929e7..519c4dd9b6f 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -1,7 +1,6 @@ import { convertCamelToUnderscore, convertTypes, - deepAccess, getBidRequest, isArray, isEmpty, @@ -14,6 +13,8 @@ import {auctionManager} from '../src/auctionManager.js'; import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'craft'; const URL_BASE = 'https://gacraft.jp/prebid-v3'; @@ -30,6 +31,9 @@ export const spec = { }, buildRequests: function(bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + const tags = bidRequests.map(bidToTag); const schain = bidRequests[0].schain; const payload = { @@ -51,7 +55,8 @@ export const spec = { } if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { - rd_ref: bidderRequest.refererInfo.referer, + // TODO: this collects everything it finds, except for the canonical URL + rd_ref: bidderRequest.refererInfo.topmostLocation, rd_top: bidderRequest.refererInfo.reachedTop, rd_ifs: bidderRequest.refererInfo.numIframes, }; @@ -136,19 +141,9 @@ function deleteValues(keyPairObj) { } } -function hasPurpose1Consent(bidderRequest) { - let result = true; - if (bidderRequest && bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { - result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); - } - } - return result; -} - function formatRequest(payload, bidderRequest) { let options = {}; - if (!hasPurpose1Consent(bidderRequest)) { + if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { options = { withCredentials: false }; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index e6f648cad0c..98aa58f5a70 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, getUniqueIdentifierStr, isArray, logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; +import { deepAccess, isArray, logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; @@ -6,6 +6,9 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { find } from '../src/polyfill.js'; import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 import { getStorageManager } from '../src/storageManager.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const GVLID = 91; export const ADAPTER_VERSION = 34; @@ -13,7 +16,7 @@ const BIDDER_CODE = 'criteo'; const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; const PROFILE_ID_INLINE = 207; export const PROFILE_ID_PUBLISHERTAG = 185; -const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); +export const storage = getStorageManager({ gvlid: GVLID, bidderCode: BIDDER_CODE }); const LOG_PREFIX = 'Criteo: '; /* @@ -31,12 +34,111 @@ const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag const FAST_BID_PUBKEY_E = 65537; const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDeaWBMxHBUT55CYyboR/EZ4efghPi3CoNGfGWezpjko9P6p2EwGArtHEeS4slhu/SpSIFMjG6fdrpRoNuIAMhq1Z+Pr/+HOd1pThFKeGFr2/NhtAg+TXAzaU='; +const SID_COOKIE_NAME = 'cto_sid'; +const IDCPY_COOKIE_NAME = 'cto_idcpy'; +const LWID_COOKIE_NAME = 'cto_lwid'; +const OPTOUT_COOKIE_NAME = 'cto_optout'; +const BUNDLE_COOKIE_NAME = 'cto_bundle'; +const GUID_RETENTION_TIME_HOUR = 24 * 30 * 13; // 13 months +const OPTOUT_RETENTION_TIME_HOUR = 5 * 12 * 30 * 24; // 5 years + /** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], + getUserSyncs: function (syncOptions, _, gdprConsent, uspConsent) { + const fastBidVersion = config.getConfig('criteo.fastBidVersion'); + if (canFastBid(fastBidVersion)) { + return []; + } + + const refererInfo = getRefererInfo(); + const origin = 'criteoPrebidAdapter'; + + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { + const queryParams = []; + queryParams.push(`origin=${origin}`); + queryParams.push(`topUrl=${refererInfo.domain}`); + if (gdprConsent) { + if (gdprConsent.gdprApplies) { + queryParams.push(`gdpr=${gdprConsent.gdprApplies == true ? 1 : 0}`); + } + if (gdprConsent.consentString) { + queryParams.push(`gdpr_consent=${gdprConsent.consentString}`); + } + } + if (uspConsent) { + queryParams.push(`us_privacy=${uspConsent}`); + } + + const requestId = Math.random().toString(); + + const jsonHash = { + bundle: readFromAllStorages(BUNDLE_COOKIE_NAME), + cw: storage.cookiesAreEnabled(), + localWebId: readFromAllStorages(LWID_COOKIE_NAME), + lsw: storage.localStorageIsEnabled(), + optoutCookie: readFromAllStorages(OPTOUT_COOKIE_NAME), + origin: origin, + requestId: requestId, + secureIdCookie: readFromAllStorages(SID_COOKIE_NAME), + tld: refererInfo.domain, + topUrl: refererInfo.domain, + uid: readFromAllStorages(IDCPY_COOKIE_NAME), + version: '$prebid.version$'.replace(/\./g, '_'), + }; + + window.addEventListener('message', function handler(event) { + if (!event.data || event.origin != 'https://gum.criteo.com') { + return; + } + + if (event.data.requestId !== requestId) { + return; + } + + this.removeEventListener('message', handler); + + event.stopImmediatePropagation(); + + const response = event.data; + + if (response.optout) { + deleteFromAllStorages(IDCPY_COOKIE_NAME); + deleteFromAllStorages(SID_COOKIE_NAME); + deleteFromAllStorages(BUNDLE_COOKIE_NAME); + deleteFromAllStorages(LWID_COOKIE_NAME); + + saveOnAllStorages(OPTOUT_COOKIE_NAME, true, OPTOUT_RETENTION_TIME_HOUR); + } else { + if (response.uid) { + saveOnAllStorages(IDCPY_COOKIE_NAME, response.uid, GUID_RETENTION_TIME_HOUR); + } + + if (response.bundle) { + saveOnAllStorages(BUNDLE_COOKIE_NAME, response.bundle, GUID_RETENTION_TIME_HOUR); + } + + if (response.removeSid) { + deleteFromAllStorages(SID_COOKIE_NAME); + } else if (response.sid) { + saveOnAllStorages(SID_COOKIE_NAME, response.sid, GUID_RETENTION_TIME_HOUR); + } + } + }, true); + + const jsonHashSerialized = JSON.stringify(jsonHash).replace(/"/g, '%22'); + + return [{ + type: 'iframe', + url: `https://gum.criteo.com/syncframe?${queryParams.join('&')}#${jsonHashSerialized}` + }]; + } + return []; + }, + /** f * @param {object} bid * @return {boolean} @@ -63,14 +165,18 @@ export const spec = { * @return {ServerRequest} */ buildRequests: (bidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + let url; let data; - let fpd = config.getConfig('ortb2') || {}; + let fpd = bidderRequest.ortb2 || {}; Object.assign(bidderRequest, { publisherExt: fpd.site?.ext, userExt: fpd.user?.ext, - ceh: config.getConfig('criteo.ceh') + ceh: config.getConfig('criteo.ceh'), + coppa: config.getConfig('coppa') }); // If publisher tag not already loaded try to get it from fast bid @@ -129,7 +235,6 @@ export const spec = { const bidId = bidRequest.bidId; const bid = { requestId: bidId, - adId: slot.bidId || getUniqueIdentifierStr(), cpm: slot.cpm, currency: slot.currency, netRevenue: true, @@ -209,6 +314,27 @@ export const spec = { }, }; +function readFromAllStorages(name) { + const fromCookie = storage.getCookie(name); + const fromLocalStorage = storage.getDataFromLocalStorage(name); + + return fromCookie || fromLocalStorage || undefined; +} + +function saveOnAllStorages(name, value, expirationTimeHours) { + const date = new Date(); + date.setTime(date.getTime() + (expirationTimeHours * 60 * 60 * 1000)); + const expires = `expires=${date.toUTCString()}`; + + storage.setCookie(name, value, expires); + storage.setDataInLocalStorage(name, value); +} + +function deleteFromAllStorages(name) { + storage.setCookie(name, '', 0); + storage.removeDataFromLocalStorage(name); +} + /** * @return {boolean} */ @@ -224,9 +350,9 @@ function publisherTagAvailable() { function buildContext(bidRequests, bidderRequest) { let referrer = ''; if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.referer; + referrer = bidderRequest.refererInfo.page; } - const queryString = parseUrl(referrer).search; + const queryString = parseUrl(bidderRequest?.refererInfo?.topmostLocation).search; const context = { url: referrer, @@ -255,6 +381,12 @@ function buildCdbUrl(context) { url += '&wv=' + encodeURIComponent('$prebid.version$'); url += '&cb=' + String(Math.floor(Math.random() * 99999999999)); + if (storage.localStorageIsEnabled()) { + url += '&lsavail=1'; + } else { + url += '&lsavail=0'; + } + if (context.amp) { url += '&im=1'; } @@ -265,6 +397,26 @@ function buildCdbUrl(context) { url += '&nolog=1'; } + const bundle = readFromAllStorages(BUNDLE_COOKIE_NAME); + if (bundle) { + url += `&bundle=${bundle}`; + } + + const optout = readFromAllStorages(OPTOUT_COOKIE_NAME); + if (optout) { + url += `&optout=1`; + } + + const sid = readFromAllStorages(SID_COOKIE_NAME); + if (sid) { + url += `&sid=${sid}`; + } + + const idcpy = readFromAllStorages(IDCPY_COOKIE_NAME); + if (idcpy) { + url += `&idcpy=${idcpy}`; + } + return url; } @@ -294,6 +446,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { url: context.url, ext: bidderRequest.publisherExt, }, + regs: { + coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined) + }, slots: bidRequests.map(bidRequest => { networkId = bidRequest.params.networkId || networkId; schain = bidRequest.schain || schain; @@ -314,15 +469,20 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.params.publisherSubId) { slot.publishersubid = bidRequest.params.publisherSubId; } - if (bidRequest.params.nativeCallback || deepAccess(bidRequest, `mediaTypes.${NATIVE}`)) { + + if (bidRequest.params.nativeCallback || hasNativeMediaType(bidRequest)) { slot.native = true; if (!checkNativeSendId(bidRequest)) { logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); } - slot.sizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'), parseNativeSize); - } else { + } + + if (hasBannerMediaType(bidRequest)) { slot.sizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'), parseSize); + } else { + slot.sizes = []; } + if (hasVideoMediaType(bidRequest)) { const video = { playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), @@ -347,6 +507,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { slot.video = video; } + + enrichSlotWithFloors(slot, bidRequest); + return slot; }), }; @@ -358,7 +521,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { ext: { schain: schain } - } + }; }; request.user = { ext: bidderRequest.userExt @@ -382,7 +545,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { return request; } -function parseSizes(sizes, parser) { +function parseSizes(sizes, parser = s => s) { if (sizes == undefined) { return []; } @@ -396,17 +559,18 @@ function parseSize(size) { return size[0] + 'x' + size[1]; } -function parseNativeSize(size) { - if (size[0] === undefined && size[1] === undefined) { - return '2x2'; - } - return size[0] + 'x' + size[1]; -} - function hasVideoMediaType(bidRequest) { return deepAccess(bidRequest, 'mediaTypes.video') !== undefined; } +function hasBannerMediaType(bidRequest) { + return deepAccess(bidRequest, 'mediaTypes.banner') !== undefined; +} + +function hasNativeMediaType(bidRequest) { + return deepAccess(bidRequest, 'mediaTypes.native') !== undefined; +} + function hasValidVideoMediaType(bidRequest) { let isValid = true; @@ -480,6 +644,42 @@ for (var i = 0; i < 10; ++i) { `; } +function enrichSlotWithFloors(slot, bidRequest) { + try { + const slotFloors = {}; + + if (bidRequest.getFloor) { + if (bidRequest.mediaTypes?.banner) { + slotFloors.banner = {}; + const bannerSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')) + bannerSizes.forEach(bannerSize => slotFloors.banner[parseSize(bannerSize).toString()] = bidRequest.getFloor({ size: bannerSize, mediaType: BANNER })); + } + + if (bidRequest.mediaTypes?.video) { + slotFloors.video = {}; + const videoSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) + videoSizes.forEach(videoSize => slotFloors.video[parseSize(videoSize).toString()] = bidRequest.getFloor({ size: videoSize, mediaType: VIDEO })); + } + + if (bidRequest.mediaTypes?.native) { + slotFloors.native = {}; + slotFloors.native['*'] = bidRequest.getFloor({ size: '*', mediaType: NATIVE }); + } + + if (Object.keys(slotFloors).length > 0) { + if (!slot.ext) { + slot.ext = {} + } + Object.assign(slot.ext, { + floors: slotFloors + }); + } + } + } catch (e) { + logError('Could not parse floors from Prebid: ' + e); + } +} + export function canFastBid(fastBidVersion) { return fastBidVersion !== FAST_BID_VERSION_NONE; } diff --git a/modules/criteoBidAdapter.md b/modules/criteoBidAdapter.md index 6a165978f3b..30ae3d97fac 100644 --- a/modules/criteoBidAdapter.md +++ b/modules/criteoBidAdapter.md @@ -27,12 +27,7 @@ Module that connects to Criteo's demand sources. ``` # Additional Config (Optional) -Set the "ceh" property to provides the user's hashed email if available -``` - pbjs.setConfig({ - criteo: { - ceh: 'hashed mail', - fastBidVersion: "none"|"latest"| - } - }); -``` + +Criteo Bid Adapter supports the collection of the user's hashed email, if available. + +Please consider passing it to the adapter, following [these guidelines](https://publisherdocs.criteotilt.com/prebid/#hashed-emails). diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index c73c4422a77..867e4315945 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -13,17 +13,18 @@ import { getStorageManager } from '../src/storageManager.js'; const gvlid = 91; const bidderCode = 'criteo'; -export const storage = getStorageManager({gvlid: gvlid, moduleName: bidderCode}); +export const storage = getStorageManager({ gvlid: gvlid, moduleName: bidderCode }); const bididStorageKey = 'cto_bidid'; const bundleStorageKey = 'cto_bundle'; +const dnaBundleStorageKey = 'cto_dna_bundle'; const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; const pastDateString = new Date(0).toString(); const expirationString = new Date(timestamp() + cookiesMaxAge).toString(); -function extractProtocolHost (url, returnOnlyHost = false) { - const parsedUrl = parseUrl(url, {noDecodeWholeURL: true}) +function extractProtocolHost(url, returnOnlyHost = false) { + const parsedUrl = parseUrl(url, { noDecodeWholeURL: true }) return returnOnlyHost ? `${parsedUrl.hostname}` : `${parsedUrl.protocol}://${parsedUrl.hostname}${parsedUrl.port ? ':' + parsedUrl.port : ''}/`; @@ -70,15 +71,17 @@ function deleteFromAllStorages(key, hostname) { function getCriteoDataFromAllStorages() { return { bundle: getFromAllStorages(bundleStorageKey), + dnaBundle: getFromAllStorages(dnaBundleStorageKey), bidId: getFromAllStorages(bididStorageKey), } } -function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) { +function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWriteable, isLocalStorageWritable, isPublishertagPresent, gdprString) { const url = 'https://gum.criteo.com/sid/json?origin=prebid' + `${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + `${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + `${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + + `${dnaBundle ? '&info=' + encodeURIComponent(dnaBundle) : ''}` + `${gdprString ? '&gdprString=' + encodeURIComponent(gdprString) : ''}` + `${areCookiesWriteable ? '&cw=1' : ''}` + `${isPublishertagPresent ? '&pbt=1' : ''}` + @@ -87,10 +90,33 @@ function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isL return url; } +function callSyncPixel(domain, pixel) { + if (pixel.writeBundleInStorage && pixel.bundlePropertyName && pixel.storageKeyName) { + ajax( + pixel.pixelUrl, + { + success: response => { + if (response) { + const jsonResponse = JSON.parse(response); + if (jsonResponse && jsonResponse[pixel.bundlePropertyName]) { + saveOnAllStorages(pixel.storageKeyName, jsonResponse[pixel.bundlePropertyName], domain); + } + } + } + }, + undefined, + { method: 'GET', withCredentials: true } + ); + } else { + triggerPixel(pixel.pixelUrl); + } +} + function callCriteoUserSync(parsedCriteoData, gdprString, callback) { const cw = storage.cookiesAreEnabled(); const lsw = storage.localStorageIsEnabled(); - const topUrl = extractProtocolHost(getRefererInfo().referer); + const topUrl = extractProtocolHost(getRefererInfo().page); + // TODO: should domain really be extracted from the current frame? const domain = extractProtocolHost(document.location.href, true); const isPublishertagPresent = typeof criteo_pubtag !== 'undefined'; // eslint-disable-line camelcase @@ -98,6 +124,7 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) { topUrl, domain, parsedCriteoData.bundle, + parsedCriteoData.dnaBundle, cw, lsw, isPublishertagPresent, @@ -107,6 +134,11 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) { const callbacks = { success: response => { const jsonResponse = JSON.parse(response); + + if (jsonResponse.pixels) { + jsonResponse.pixels.forEach(pixel => callSyncPixel(domain, pixel)); + } + if (jsonResponse.acwsUrl) { const urlsToCall = typeof jsonResponse.acwsUrl === 'string' ? [jsonResponse.acwsUrl] : jsonResponse.acwsUrl; urlsToCall.forEach(url => triggerPixel(url)); diff --git a/modules/currency.js b/modules/currency.js index 289ec8fbf69..5b0f9af7afc 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -5,7 +5,7 @@ import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; -import {promiseControls} from '../src/utils/promise.js'; +import {defer} from '../src/utils/promise.js'; const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$'; const CURRENCY_RATE_PRECISION = 4; @@ -24,7 +24,7 @@ var defaultRates; export const ready = (() => { let ctl; function reset() { - ctl = promiseControls(); + ctl = defer(); } reset(); return {done: () => ctl.resolve(), reset, promise: () => ctl.promise} @@ -146,6 +146,7 @@ function initCurrency(url) { try { currencyRates = JSON.parse(response); logInfo('currencyRates set to ' + JSON.stringify(currencyRates)); + conversionCache = {}; currencyRatesLoaded = true; processBidResponseQueue(); ready.done(); diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index c0a24b49a3c..a11899609bc 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -1,5 +1,4 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {OUTSTREAM} from '../src/video.js'; @@ -11,6 +10,7 @@ import { getValue, isArray, isNumber, + isStr, logError, logWarn, parseSizesInput, @@ -130,6 +130,11 @@ export const spec = { isBidRequestValid: function(bid) { bid.params = bid.params || {}; + if (bid.params.cwcreative && !isStr(bid.params.cwcreative)) { + logError('cwcreative must be of type string!'); + return false; + } + if (!bid.params.placementId || !isNumber(bid.params.placementId)) { logError('placementId not provided or invalid'); return false; @@ -170,7 +175,7 @@ export const spec = { let slots = []; let referer; try { - referer = getRefererInfo().referer; + referer = bidderRequest?.refererInfo?.page; slots = mapSlotsData(validBidRequests); } catch (e) { logWarn(e); @@ -178,7 +183,7 @@ export const spec = { let refgroups = []; - const cwCreativeId = parseInt(getQueryVariable(CW_CREATIVE_QUERY), 10) || null; + const cwCreative = getQueryVariable(CW_CREATIVE_QUERY) || null; const cwCreativeIdFromConfig = this.getFirstValueOrNull(slots, 'cwcreative'); const refGroupsFromConfig = this.getFirstValueOrNull(slots, 'refgroups'); const cwApiKeyFromConfig = this.getFirstValueOrNull(slots, 'cwapikey'); @@ -199,7 +204,7 @@ export const spec = { const payload = { cwid: localStorageCWID, refgroups, - cwcreative: cwCreativeId || cwCreativeIdFromConfig, + cwcreative: cwCreative || cwCreativeIdFromConfig, slots: slots, cwapikey: cwApiKeyFromConfig, httpRef: referer || '', @@ -298,5 +303,5 @@ export const spec = { return bidResponses; }, -} +}; registerBidder(spec); diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md index b42c7a02489..188894cd202 100644 --- a/modules/cwireBidAdapter.md +++ b/modules/cwireBidAdapter.md @@ -13,12 +13,12 @@ Connects to C-WIRE demand source to fetch bids. Below, the list of C-WIRE params and where they can be set. -| Param name | Global config | AdUnit config | Type | Required | -| ---------- | ------------- | ------------- | ---- | ---------| +| Param name | Global config | AdUnit config | Type | Required | +| ---------- | ------------- | ------------- |--------| ---------| | pageId | | x | number | YES | | placementId | | x | number | YES | | refgroups | | x | string | NO | -| cwcreative | | x | integer | NO | +| cwcreative | | x | string | NO | | cwapikey | | x | string | NO | @@ -38,7 +38,7 @@ var adUnits = [ params: { pageId: 1422, // required - number placementId: 2211521, // required - number - cwcreative: 42, // optional - id of creative to force + cwcreative: '42', // optional - id of creative to force refgroups: 'test-user', // optional - name of group or coma separated list of groups to force cwapikey: 'api_key_xyz', // optional - api key for integration testing } diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js index ffa84ff88fd..5ae8ee3ea21 100644 --- a/modules/dailyhuntBidAdapter.js +++ b/modules/dailyhuntBidAdapter.js @@ -4,6 +4,7 @@ import {_map, deepAccess, isEmpty} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {find} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'dailyhunt'; const BIDDER_ALIAS = 'dh'; @@ -96,7 +97,7 @@ const flatten = (arr) => { const createOrtbRequest = (validBidRequests, bidderRequest) => { let device = createOrtbDeviceObj(validBidRequests); let user = createOrtbUserObj(validBidRequests) - let site = createOrtbSiteObj(validBidRequests, bidderRequest.refererInfo.referer) + let site = createOrtbSiteObj(validBidRequests, bidderRequest.refererInfo.page) return { id: bidderRequest.auctionId, imp: [], @@ -384,6 +385,9 @@ export const spec = { isBidRequestValid: bid => !!bid.params.placement_id && !!bid.params.publisher_id && !!bid.params.partner_name, buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let serverRequests = []; // ORTB Request. diff --git a/modules/danmarketBidAdapter.md b/modules/danmarketBidAdapter.md deleted file mode 100644 index 8ddc83d2cf6..00000000000 --- a/modules/danmarketBidAdapter.md +++ /dev/null @@ -1,40 +0,0 @@ -# Overview - -Module Name: Dentsu Aegis Network Marketplace Bidder Adapter -Module Type: Bidder Adapter -Maintainer: niels@baarsma.net - -# Description - -Module that connects to DAN Marketplace demand source to fetch bids. - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: "danmarket", - params: { - uid: '4', - priceType: 'gross' // by default is 'net' - } - } - ] - },{ - code: 'test-div', - sizes: [[728, 90]], - bids: [ - { - bidder: "danmarket", - params: { - uid: 5, - priceType: 'gross' - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/dataControllerModule/index.js b/modules/dataControllerModule/index.js new file mode 100644 index 00000000000..c9d15457c3d --- /dev/null +++ b/modules/dataControllerModule/index.js @@ -0,0 +1,195 @@ +/** + * This module validates the configuration and filters data accordingly + * @module modules/dataController + */ +import {config} from '../../src/config.js'; +import {getHook, module} from '../../src/hook.js'; +import {deepAccess, deepSetValue, prefixLog} from '../../src/utils.js'; +import {startAuction} from '../../src/prebid.js'; + +const LOG_PRE_FIX = 'Data_Controller : '; +const ALL = '*'; +const MODULE_NAME = 'dataController'; +const GLOBAL = {}; +let _dataControllerConfig; + +const _logger = prefixLog(LOG_PRE_FIX); + +/** + * BidderRequests hook to intiate module and reset data object + */ +export function filterBidData(fn, req) { + if (_dataControllerConfig.filterEIDwhenSDA) { + filterEIDs(req.adUnits, req.ortb2Fragments); + } + + if (_dataControllerConfig.filterSDAwhenEID) { + filterSDA(req.adUnits, req.ortb2Fragments); + } + fn.call(this, req); + return req; +} + +function containsConfiguredEIDS(eidSourcesMap, bidderCode) { + if (_dataControllerConfig.filterSDAwhenEID.includes(ALL)) { + return true; + } + let bidderEIDs = eidSourcesMap.get(bidderCode); + if (bidderEIDs == undefined) { + return false; + } + let containsEIDs = false; + _dataControllerConfig.filterSDAwhenEID.some(source => { + if (bidderEIDs.has(source)) { + containsEIDs = true; + } + }); + return containsEIDs; +} + +function containsConfiguredSDA(segementMap, bidderCode) { + if (_dataControllerConfig.filterEIDwhenSDA.includes(ALL)) { + return true; + } + return hasValue(segementMap.get(bidderCode)) || hasValue(segementMap.get(GLOBAL)) +} + +function hasValue(bidderSegement) { + let containsSDA = false; + if (bidderSegement == undefined) { + return false; + } + _dataControllerConfig.filterEIDwhenSDA.some(segment => { + if (bidderSegement.has(segment)) { + containsSDA = true; + } + }); + return containsSDA; +} + +function getSegmentConfig(ortb2Fragments) { + let bidderSDAMap = new Map(); + let globalObject = deepAccess(ortb2Fragments, 'global') || {}; + + collectSegments(bidderSDAMap, GLOBAL, globalObject); + if (ortb2Fragments.bidder) { + for (const [key, value] of Object.entries(ortb2Fragments.bidder)) { + collectSegments(bidderSDAMap, key, value); + } + } + return bidderSDAMap; +} + +function collectSegments(bidderSDAMap, key, data) { + let segmentSet = constructSegment(deepAccess(data, 'user.data') || []); + if (segmentSet && segmentSet.size > 0) bidderSDAMap.set(key, segmentSet); +} + +function constructSegment(userData) { + let segmentSet; + if (userData) { + segmentSet = new Set(); + for (let i = 0; i < userData.length; i++) { + let segments = userData[i].segment; + let segmentPrefix = ''; + if (userData[i].name) { + segmentPrefix = userData[i].name + ':'; + } + + if (userData[i].ext && userData[i].ext.segtax) { + segmentPrefix += userData[i].ext.segtax + ':'; + } + for (let j = 0; j < segments.length; j++) { + segmentSet.add(segmentPrefix + segments[j].id); + } + } + } + + return segmentSet; +} + +function getEIDsSource(adUnits) { + let bidderEIDSMap = new Map(); + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + let userEIDs = deepAccess(bid, 'userIdAsEids') || []; + + if (userEIDs) { + let sourceSet = new Set(); + for (let i = 0; i < userEIDs.length; i++) { + let source = userEIDs[i].source; + sourceSet.add(source); + } + bidderEIDSMap.set(bid.bidder, sourceSet); + } + }); + }); + + return bidderEIDSMap; +} + +function filterSDA(adUnits, ortb2Fragments) { + let bidderEIDSMap = getEIDsSource(adUnits); + let resetGlobal = false; + for (const [key, value] of Object.entries(ortb2Fragments.bidder)) { + let resetSDA = containsConfiguredEIDS(bidderEIDSMap, key); + if (resetSDA) { + deepSetValue(value, 'user.data', []); + resetGlobal = true; + } + } + if (resetGlobal) { + deepSetValue(ortb2Fragments, 'global.user.data', []) + } +} + +function filterEIDs(adUnits, ortb2Fragments) { + let segementMap = getSegmentConfig(ortb2Fragments); + let globalEidUpdate = false; + adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + let resetEID = containsConfiguredSDA(segementMap, bid.bidder); + if (resetEID) { + globalEidUpdate = true; + bid.userIdAsEids = []; + bid.userId = {}; + if (ortb2Fragments.bidder) { + let bidderFragment = ortb2Fragments.bidder[bid.bidder]; + let userExt = deepAccess(bidderFragment, 'user.ext.eids') || []; + if (userExt) { + deepSetValue(bidderFragment, 'user.ext.eids', []) + } + } + } + }); + }); + + if (globalEidUpdate) { + deepSetValue(ortb2Fragments, 'global.user.ext.eids', []) + } + return adUnits; +} + +export function init() { + const confListener = config.getConfig(MODULE_NAME, dataControllerConfig => { + const dataController = dataControllerConfig && dataControllerConfig.dataController; + if (!dataController) { + _logger.logInfo(`Data Controller is not configured`); + startAuction.getHooks({hook: filterBidData}).remove(); + return; + } + + if (dataController.filterEIDwhenSDA && dataController.filterSDAwhenEID) { + _logger.logInfo(`Data Controller can be configured with either filterEIDwhenSDA or filterSDAwhenEID`); + startAuction.getHooks({hook: filterBidData}).remove(); + return; + } + confListener(); // unsubscribe config listener + _dataControllerConfig = dataController; + + getHook('startAuction').before(filterBidData); + }); +} + +init(); +module(MODULE_NAME, init); diff --git a/modules/dataControllerModule/index.md b/modules/dataControllerModule/index.md new file mode 100644 index 00000000000..8714b886b0e --- /dev/null +++ b/modules/dataControllerModule/index.md @@ -0,0 +1,29 @@ +# Overview + +``` +Module Name: Data Controller Module +``` + +# Description + +This module will filter EIDs and SDA based on the configurations. + +Sub module object with the following keys: + +| param name | type | Scope | Description | Params | +| :------------ | :------------ | :------ | :------ | :------ | +| filterEIDwhenSDA | function | optional | Filters user EIDs based on SDA | bidrequest | +| filterSDAwhenEID | function | optional | Filters SDA based on configured EIDs | bidrequest | + +# Module Control Configuration + +``` + +pbjs.setConfig({ + dataController: { + filterEIDwhenSDA: ['*'] + filterSDAwhenEID: ['id5-sync.com'] + } +}); + +``` diff --git a/modules/datablocksAnalyticsAdapter.js b/modules/datablocksAnalyticsAdapter.js index 5e977155284..61933cc45e9 100644 --- a/modules/datablocksAnalyticsAdapter.js +++ b/modules/datablocksAnalyticsAdapter.js @@ -2,7 +2,7 @@ * Analytics Adapter for Datablocks */ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; var datablocksAdapter = adapter({ diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index b240db1dd25..4e21e08ba57 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -4,6 +4,7 @@ import { config } from '../src/config.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { ajax } from '../src/ajax.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; export const storage = getStorageManager({bidderCode: 'datablocks'}); const NATIVE_ID_MAP = {}; @@ -252,6 +253,9 @@ export const spec = { // GENERATE THE RTB REQUEST buildRequests: function(validRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validRequests = convertOrtbRequestToProprietaryNative(validRequests); + // RETURN EMPTY IF THERE ARE NO VALID REQUESTS if (!validRequests.length) { return []; @@ -347,16 +351,17 @@ export const spec = { // GENERATE SITE OBJECT let site = { domain: window.location.host, - page: bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + page: bidderRequest.refererInfo.page, schain: validRequests[0].schain || {}, ext: { - p_domain: config.getConfig('publisherDomain'), + p_domain: bidderRequest.refererInfo.domain, rt: bidderRequest.refererInfo.reachedTop, frames: bidderRequest.refererInfo.numIframes, stack: bidderRequest.refererInfo.stack, timeout: config.getConfig('bidderTimeout') }, - } + }; // ADD REF URL IF FOUND if (self === top && document.referrer) { @@ -383,7 +388,7 @@ export const spec = { gdpr: bidderRequest.gdprConsent || {}, usp: bidderRequest.uspConsent || {}, client_info: this.get_client_info(), - ortb2: config.getConfig('ortb2') || {} + ortb2: bidderRequest.ortb2 || {} } }; diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js new file mode 100644 index 00000000000..71870d6437d --- /dev/null +++ b/modules/datawrkzBidAdapter.js @@ -0,0 +1,633 @@ +import { deepAccess, getBidIdParameter, isArray, getUniqueIdentifierStr, contains } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { Renderer } from '../src/Renderer.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { createBid } from '../src/bidfactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import CONSTANTS from '../src/constants.json'; +import { OUTSTREAM, INSTREAM } from '../src/video.js'; + +const BIDDER_CODE = 'datawrkz'; +const ALIASES = []; +const ENDPOINT_URL = 'https://at.datawrkz.com/exchange/openrtb23/'; +const RENDERER_URL = 'https://js.datawrkz.com/prebid/osRenderer.min.js'; +const OUTSTREAM_TYPES = ['inline', 'slider_top_left', 'slider_top_right', 'slider_bottom_left', 'slider_bottom_right', 'interstitial_close', 'listicle'] +const OUTSTREAM_MIMES = ['video/mp4'] +const SUPPORTED_AD_TYPES = [BANNER, NATIVE, VIDEO]; + +export const spec = { + code: BIDDER_CODE, + aliases: ALIASES, + supportedMediaTypes: SUPPORTED_AD_TYPES, + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.site_id && (deepAccess(bid, 'mediaTypes.video.context') != 'adpod')); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + let requests = []; + + if (validBidRequests.length > 0) { + validBidRequests.forEach(bidRequest => { + if (!bidRequest.mediaTypes) return; + if (bidRequest.mediaTypes.banner && ((bidRequest.mediaTypes.banner.sizes && bidRequest.mediaTypes.banner.sizes.length != 0) || + (bidRequest.sizes))) { + requests.push(buildBannerRequest(bidRequest, bidderRequest)); + } else if (bidRequest.mediaTypes.native) { + requests.push(buildNativeRequest(bidRequest, bidderRequest)); + } else if (bidRequest.mediaTypes.video) { + requests.push(buildVideoRequest(bidRequest, bidderRequest)); + } + }); + } + return requests; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, request) { + var bidResponses = []; + let bidRequest = request.bidRequest + let bidResponse = serverResponse.body; + + // valid object? + if ((!bidResponse || !bidResponse.id) || (!bidResponse.seatbid || bidResponse.seatbid.length === 0 || + !bidResponse.seatbid[0].bid || bidResponse.seatbid[0].bid.length === 0)) { + return []; + } + + if (getMediaTypeOfResponse(bidRequest) == BANNER) { + bidResponses = buildBannerResponse(bidRequest, bidResponse); + } else if (getMediaTypeOfResponse(bidRequest) == NATIVE) { + bidResponses = buildNativeResponse(bidRequest, bidResponse); + } else if (getMediaTypeOfResponse(bidRequest) == VIDEO) { + bidResponses = buildVideoResponse(bidRequest, bidResponse); + } + return bidResponses; + }, +} + +/* Generate bid request for banner adunit */ +function buildBannerRequest(bidRequest, bidderRequest) { + let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + + let adW = 0; + let adH = 0; + + let bannerSizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); + let bidSizes = isArray(bannerSizes) ? bannerSizes : bidRequest.sizes; + if (isArray(bidSizes)) { + if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { + adW = parseInt(bidSizes[0]); + adH = parseInt(bidSizes[1]); + } else { + adW = parseInt(bidSizes[0][0]); + adH = parseInt(bidSizes[0][1]); + } + } + + var deals = []; + if (bidRequest.params.deals && bidRequest.params.deals.length > 0) { + deals = bidRequest.params.deals; + } + + const imp = [{ + id: bidRequest.bidId, + banner: { + w: adW, + h: adH + }, + bidfloor: bidFloor, + pmp: { + deals: deals + } + }]; + + bidRequest.requestedMediaType = BANNER; + const scriptUrl = generateScriptUrl(bidRequest); + const payloadString = generatePayload(imp, bidderRequest); + + return { + method: 'POST', + url: scriptUrl, + data: payloadString, + bidRequest + }; +} + +/* Generate bid request for native adunit */ +function buildNativeRequest(bidRequest, bidderRequest) { + let counter = 0; + let assets = []; + + let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + + let title = deepAccess(bidRequest, 'mediaTypes.native.title'); + if (title && title.len) { + assets.push(generateNativeTitleObj(title, ++counter)); + } + let image = deepAccess(bidRequest, 'mediaTypes.native.image'); + if (image) { + assets.push(generateNativeImgObj(image, 'image', ++counter)); + } + let icon = deepAccess(bidRequest, 'mediaTypes.native.icon'); + if (icon) { + assets.push(generateNativeImgObj(icon, 'icon', ++counter)); + } + let sponsoredBy = deepAccess(bidRequest, 'mediaTypes.native.sponsoredBy'); + if (sponsoredBy) { + assets.push(generateNativeDataObj(sponsoredBy, 'sponsored', ++counter)); + } + let cta = deepAccess(bidRequest, 'mediaTypes.native.cta'); + if (cta) { + assets.push(generateNativeDataObj(cta, 'cta', ++counter)); + } + let body = deepAccess(bidRequest, 'mediaTypes.native.body'); + if (body) { + assets.push(generateNativeDataObj(body, 'desc', ++counter)); + } + + let request = JSON.stringify({assets: assets}); + const native = { + request: request + }; + + var deals = []; + if (bidRequest.params.deals && bidRequest.params.deals.length > 0) { + deals = bidRequest.params.deals; + } + + const imp = [{ + id: bidRequest.bidId, + native: native, + bidfloor: bidFloor, + pmp: { + deals: deals + } + }]; + + bidRequest.requestedMediaType = NATIVE; + bidRequest.assets = assets; + const scriptUrl = generateScriptUrl(bidRequest); + const payloadString = generatePayload(imp, bidderRequest); + + return { + method: 'POST', + url: scriptUrl, + data: payloadString, + bidRequest + }; +} + +/* Generate bid request for video adunit */ +function buildVideoRequest(bidRequest, bidderRequest) { + let bidFloor = Number(getBidIdParameter('bidfloor', bidRequest.params)); + + let sizeObj = getVideoAdUnitSize(bidRequest); + + const video = { + w: sizeObj.adW, + h: sizeObj.adH, + api: deepAccess(bidRequest, 'mediaTypes.video.api'), + mimes: deepAccess(bidRequest, 'mediaTypes.video.mimes'), + protocols: deepAccess(bidRequest, 'mediaTypes.video.protocols'), + playbackmethod: deepAccess(bidRequest, 'mediaTypes.video.playbackmethod'), + minduration: deepAccess(bidRequest, 'mediaTypes.video.minduration'), + maxduration: deepAccess(bidRequest, 'mediaTypes.video.maxduration'), + startdelay: deepAccess(bidRequest, 'mediaTypes.video.startdelay'), + minbitrate: deepAccess(bidRequest, 'mediaTypes.video.minbitrate'), + maxbitrate: deepAccess(bidRequest, 'mediaTypes.video.maxbitrate'), + delivery: deepAccess(bidRequest, 'mediaTypes.video.delivery'), + linearity: deepAccess(bidRequest, 'mediaTypes.video.linearity'), + placement: deepAccess(bidRequest, 'mediaTypes.video.placement'), + skip: deepAccess(bidRequest, 'mediaTypes.video.skip'), + skipafter: deepAccess(bidRequest, 'mediaTypes.video.skipafter') + }; + + let context = deepAccess(bidRequest, 'mediaTypes.video.context'); + if (context == 'outstream' && !bidRequest.renderer) video.mimes = OUTSTREAM_MIMES; + + var imp = []; + var deals = []; + if (bidRequest.params.deals && bidRequest.params.deals.length > 0) { + deals = bidRequest.params.deals; + } + + if (context != 'adpod') { + imp.push({ + id: bidRequest.bidId, + video: video, + bidfloor: bidFloor, + pmp: { + deals: deals + } + }); + } + bidRequest.requestedMediaType = VIDEO; + const scriptUrl = generateScriptUrl(bidRequest); + const payloadString = generatePayload(imp, bidderRequest); + + return { + method: 'POST', + url: scriptUrl, + data: payloadString, + bidRequest + }; +} + +/* Convert video player size to bid request compatible format */ +function getVideoAdUnitSize(bidRequest) { + var adH = 0; + var adW = 0; + let playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + if (isArray(playerSize)) { + if (playerSize.length === 2 && typeof playerSize[0] === 'number' && typeof playerSize[1] === 'number') { + adW = parseInt(playerSize[0]); + adH = parseInt(playerSize[1]); + } else { + adW = parseInt(playerSize[0][0]); + adH = parseInt(playerSize[0][1]); + } + } + return {adH: adH, adW: adW} +} + +/* Get mediatype of the adunit from request */ +function getMediaTypeOfResponse(bidRequest) { + if (bidRequest.requestedMediaType == BANNER) return BANNER; + else if (bidRequest.requestedMediaType == NATIVE) return NATIVE; + else if (bidRequest.requestedMediaType == VIDEO) return VIDEO; + else return ''; +} + +/* Generate endpoint url */ +function generateScriptUrl(bidRequest) { + let queryParams = 'hb=1'; + let siteId = getBidIdParameter('site_id', bidRequest.params); + return ENDPOINT_URL + siteId + '?' + queryParams; +} + +/* Generate request payload for the adunit */ +function generatePayload(imp, bidderRequest) { + let domain = window.location.host; + let page = window.location.host + window.location.pathname + location.search + location.hash; + + const site = { + domain: domain, + page: page, + publisher: {} + }; + + let regs = {ext: {}}; + + if (bidderRequest.uspConsent) { + regs.ext.us_privacy = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent && typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? '1' : '0'; + } + + if (config.getConfig('coppa') === true) { + regs.coppa = '1'; + } + + const device = { + ua: window.navigator.userAgent + }; + + const payload = { + id: getUniqueIdentifierStr(), + imp: imp, + site: site, + device: device, + regs: regs + }; + + return JSON.stringify(payload); +} + +/* Generate image asset object */ +function generateNativeImgObj(obj, type, id) { + let adW = 0; + let adH = 0; + let bidSizes = obj.sizes; + + var typeId; + if (type == 'icon') typeId = 1; + else if (type == 'image') typeId = 3; + + if (isArray(bidSizes)) { + if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { + adW = parseInt(bidSizes[0]); + adH = parseInt(bidSizes[1]); + } else { + adW = parseInt(bidSizes[0][0]); + adH = parseInt(bidSizes[0][1]); + } + } + + let required = obj.required ? 1 : 0; + let image = { + type: parseInt(typeId), + w: adW, + h: adH + }; + return { + id: id, + required: required, + img: image + }; +} + +/* Generate title asset object */ +function generateNativeTitleObj(obj, id) { + let required = obj.required ? 1 : 0; + let title = { + len: obj.len + }; + return { + id: id, + required: required, + title: title + }; +} + +/* Generate data asset object */ +function generateNativeDataObj(obj, type, id) { + var typeId; + switch (type) { + case 'sponsored': typeId = 1; + break; + case 'desc' : typeId = 2; + break; + case 'cta' : typeId = 12; + break; + } + + let required = obj.required ? 1 : 0; + let data = { + type: typeId + }; + if (typeId == 2 && obj.len) { + data.len = parseInt(obj.len); + } + return { + id: id, + required: required, + data: data + }; +} + +/* Convert banner bid response to compatible format */ +function buildBannerResponse(bidRequest, bidResponse) { + const bidResponses = []; + bidResponse.seatbid[0].bid.forEach(function (bidderBid) { + let responseCPM; + let placementCode = ''; + + if (bidRequest) { + let bidResponse = createBid(1); + placementCode = bidRequest.placementCode; + bidRequest.status = CONSTANTS.STATUS.GOOD; + responseCPM = parseFloat(bidderBid.price); + if (responseCPM === 0 || isNaN(responseCPM)) { + let bid = createBid(2); + bid.requestId = bidRequest.bidId; + bid.bidderCode = bidRequest.bidder; + bidResponses.push(bid); + return; + } + let bidSizes = (deepAccess(bidRequest, 'mediaTypes.banner.sizes')) ? deepAccess(bidRequest, 'mediaTypes.banner.sizes') : bidRequest.sizes; + bidResponse.requestId = bidRequest.bidId; + bidResponse.transactionId = bidRequest.transactionId; + bidResponse.placementCode = placementCode; + bidResponse.cpm = responseCPM; + bidResponse.size = bidSizes; + bidResponse.width = parseInt(bidderBid.w); + bidResponse.height = parseInt(bidderBid.h); + let responseAd = bidderBid.adm; + let responseNurl = ''; + bidResponse.ad = decodeURIComponent(responseAd + responseNurl); + bidResponse.creativeId = bidderBid.id; + bidResponse.bidderCode = bidRequest.bidder; + bidResponse.ttl = 300; + bidResponse.netRevenue = true; + bidResponse.currency = 'USD'; + bidResponse.mediaType = BANNER; + bidResponses.push(bidResponse); + } + }); + return bidResponses; +} + +/* Convert native bid response to compatible format */ +function buildNativeResponse(bidRequest, response) { + const bidResponses = []; + response.seatbid[0].bid.forEach(function (bidderBid) { + let responseCPM; + let placementCode = ''; + + if (bidRequest) { + let bidResponse = createBid(1); + placementCode = bidRequest.placementCode; + bidRequest.status = CONSTANTS.STATUS.GOOD; + responseCPM = parseFloat(bidderBid.price); + if (responseCPM === 0 || isNaN(responseCPM)) { + let bid = createBid(2); + bid.requestId = bidRequest.bidId; + bid.bidderCode = bidRequest.bidder; + bidResponses.push(bid); + return; + } + bidResponse.requestId = bidRequest.bidId; + bidResponse.transactionId = bidRequest.transactionId; + bidResponse.placementCode = placementCode; + bidResponse.cpm = responseCPM; + + let nativeResponse = JSON.parse(bidderBid.adm).native; + + const native = { + clickUrl: nativeResponse.link.url, + impressionTrackers: nativeResponse.imptrackers + }; + + nativeResponse.assets.forEach(function(asset) { + let keyVal = getNativeAssestObj(asset, bidRequest.assets); + native[keyVal.key] = keyVal.value; + }); + + bidResponse.creativeId = bidderBid.id; + bidResponse.bidderCode = bidRequest.bidder; + bidResponse.ttl = 300; + if (bidRequest.sizes) { bidResponse.size = bidRequest.sizes; } + bidResponse.netRevenue = true; + bidResponse.currency = 'USD'; + bidResponse.native = native; + bidResponse.mediaType = NATIVE; + bidResponses.push(bidResponse); + } + }); + return bidResponses; +} + +/* Convert video bid response to compatible format */ +function buildVideoResponse(bidRequest, response) { + const bidResponses = []; + response.seatbid[0].bid.forEach(function (bidderBid) { + let responseCPM; + let placementCode = ''; + + if (bidRequest) { + let bidResponse = createBid(1); + placementCode = bidRequest.placementCode; + bidRequest.status = CONSTANTS.STATUS.GOOD; + responseCPM = parseFloat(bidderBid.price); + if (responseCPM === 0 || isNaN(responseCPM)) { + let bid = createBid(2); + bid.requestId = bidRequest.bidId; + bid.bidderCode = bidRequest.bidder; + bidResponses.push(bid); + return; + } + let context = bidRequest.mediaTypes.video.context; + + bidResponse.requestId = bidRequest.bidId; + bidResponse.transactionId = bidRequest.transactionId; + bidResponse.placementCode = placementCode; + bidResponse.cpm = responseCPM; + + let vastXml = decodeURIComponent(bidderBid.adm); + + bidResponse.creativeId = bidderBid.id; + bidResponse.bidderCode = bidRequest.bidder; + bidResponse.ttl = 300; + bidResponse.netRevenue = true; + bidResponse.currency = 'USD'; + var ext = bidderBid.ext; + var vastUrl = ''; + if (ext) { + vastUrl = ext.vast_url; + } + var adUnitCode = bidRequest.adUnitCode; + var sizeObj = getVideoAdUnitSize(bidRequest); + + bidResponse.height = sizeObj.adH; + bidResponse.width = sizeObj.adW; + + switch (context) { + case OUTSTREAM: + var outstreamType = contains(OUTSTREAM_TYPES, bidRequest.params.outstreamType) ? bidRequest.params.outstreamType : ''; + bidResponse.outstreamType = outstreamType; + bidResponse.ad = vastXml; + if (!bidRequest.renderer) { + const renderer = Renderer.install({ + id: bidderBid.id, + url: RENDERER_URL, + config: bidRequest.params.outstreamConfig || {}, + loaded: false, + adUnitCode + }); + renderer.setRender(outstreamRender); + bidResponse.renderer = renderer; + } else { bidResponse.adResponse = vastXml; } + break; + case INSTREAM: + bidResponse.vastUrl = vastUrl; + bidResponse.adserverTargeting = setTargeting(vastUrl); + break; + } + bidResponse.mediaType = VIDEO; + bidResponses.push(bidResponse); + } + }); + return bidResponses; +} + +/* Generate renderer for outstream ad unit */ +function outstreamRender(bid) { + bid.renderer.push(() => { + window.osRenderer({ + adResponse: bid.ad, + height: bid.height, + width: bid.width, + targetId: bid.adUnitCode, // target div id to render video + outstreamType: bid.outstreamType, + options: bid.renderer.getConfig(), + }); + }); +} + +/* Set targeting params used for instream video that is required to generate cache url */ +function setTargeting(query) { + var targeting = {}; + var hash; + var hashes = query.slice(query.indexOf('?') + 1).split('&'); + for (var i = 0; i < hashes.length; i++) { + hash = hashes[i].split('='); + targeting['hb_' + hash[0]] = hash[1]; + } + return targeting; +} + +/* Get image type with respect to the id */ +function getAssetImageType(id, assets) { + for (var i = 0; i < assets.length; i++) { + if (assets[i].id == id) { + if (assets[i].img.type == 1) { return 'icon'; } else if (assets[i].img.type == 3) { return 'image'; } + } + } + return ''; +} + +/* Get type of data asset with respect to the id */ +function getAssetDataType(id, assets) { + for (var i = 0; i < assets.length; i++) { + if (assets[i].id == id) { + if (assets[i].data.type == 1) { return 'sponsored'; } else if (assets[i].data.type == 2) { return 'desc'; } else if (assets[i].data.type == 12) { return 'cta'; } + } + } + return ''; +} + +/* Convert response assests to compatible format */ +function getNativeAssestObj(obj, assets) { + if (obj.title) { + return { + key: 'title', + value: obj.title.text + } + } + if (obj.data) { + return { + key: getAssetDataType(obj.id, assets), + value: obj.data.value + } + } + if (obj.img) { + return { + key: getAssetImageType(obj.id, assets), + value: { + url: obj.img.url, + height: obj.img.h, + width: obj.img.w + } + } + } +} + +registerBidder(spec); diff --git a/modules/datawrkzBidAdapter.md b/modules/datawrkzBidAdapter.md new file mode 100644 index 00000000000..85ea43e6dd3 --- /dev/null +++ b/modules/datawrkzBidAdapter.md @@ -0,0 +1,160 @@ +# Overview + +``` +Module Name: Datawrkz Bid Adapter +Module Type: Bidder Adapter +Maintainer: pubops@datawrkz.com +``` + +# Description + +Module that connects to Datawrkz's demand sources. +Datawrkz bid adapter supports Banner, Video (instream and outstream) and Native ad units. + +# Bid Parameters + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `site_id` | required | String | Site id | "test_site_id" +| `deals` | optional | Array | Array of deal objects | `[{id: "deal_1"},{id: "deal_2"}]` +| `bidfloor` | optional | Float | Minimum bid for this impression expressed in CPM | `0.5` +| `outstreamType` | optional | String | Type of outstream video to the played. Available options: inline, slider_top_left, slider_top_right, slider_bottom_left, slider_bottom_right, interstitial_close, and listicle | "inline" +| `outstreamConfig` | optional | Object | Configuration settings for outstream ad unit | `{ad_unit_audio: 1, show_player_close_button_after: 5, hide_player_control: 0}` + +# Deal Object +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `id` | required | String | Deal id | "test_deal_id" + +# outstreamConfig +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `ad_unit_audio` | optional | Integer | Set default audio option for the player. 0 to play audio on hover and 2 to mute | `0` or `2` +| `show_player_close_button_after` | optional | Integer | Show player close button after specified seconds | `5` +| `hide_player_control` | optional | Integer | Show/hide player controls. 0 to show player controls and 1 to hide | `1` + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'datawrkz', + params: { + site_id: 'site_id', + bidfloor: 0.5 + } + }] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + image: { + required: true, + sizes: [300, 250] + }, + icon: { + required: true, + sizes: [50, 50] + }, + body: { + required: true, + len: 800 + }, + sponsoredBy: { + required: true + }, + cta: { + required: true + } + } + }, + bids: [{ + bidder: 'datawrkz', + params: { + site_id: 'site_id', + bidfloor: 0.5 + } + }] + }, + // Video instream adUnit + { + code: 'video-instream', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream', + api: [1, 2], + mimes: ["video/x-ms-wmv", "video/mp4"], + protocols: [1, 2, 3], + playbackmethod: [1, 2], + minduration: 20, + maxduration: 30, + startdelay: 5, + minbitrate: 300, + maxbitrate: 1500, + delivery: [2], + linearity: 1 + }, + }, + bids: [{ + bidder: 'datawrkz', + params: { + site_id: 'site_id', + bidfloor: 0.5 + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream', + api: [1, 2], + mimes: ["video/mp4"], + protocols: [1, 2, 3], + playbackmethod: [1, 2], + minduration: 20, + maxduration: 30, + startdelay: 5, + minbitrate: 300, + maxbitrate: 1500, + delivery: [2], + linearity: 1 + } + }, + bids: [ + { + bidder: 'datawrkz', + params: { + site_id: 'site_id', + bidfloor: 0.5, + outstreamType: 'slider_top_left', //Supported types : inline, slider_top_left, slider_top_right, slider_bottom_left, slider_bottom_right, interstitial_close, listicle + outstreamConfig: { + ad_unit_audio: 1, // 0: audio on hover, 2: always muted + show_player_close_button_after: 5, // show close button after 5 seconds + hide_player_control: 0 // 0 to show/ 1 to hide + } + } + } + ] + } +]; +``` diff --git a/modules/debugging/WARNING.md b/modules/debugging/WARNING.md new file mode 100644 index 00000000000..109d6db7704 --- /dev/null +++ b/modules/debugging/WARNING.md @@ -0,0 +1,9 @@ +## Warning + +This module is also packaged as a "standalone" .js file and loaded dynamically by prebid-core when debugging configuration is passed to `setConfig` or loaded from session storage. + +"Standalone" means that it does not have a compile-time dependency on `prebid-core.js` and can therefore work even if it was not built together with it (as would be the case when Prebid is pulled from npm). + +Because of this, **this module cannot freely import symbols from core**: anything that depends on Prebid global state (which includes, but is not limited to, `config`, `auctionManager`, `adapterManager`, etc) would *not* work as expected. + +Imports must be limited to logic that is stateless and free of side effects; symbols from `utils.js` are mostly OK, with the notable exception of logging functions (which have a dependency on `config`). diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js index 2a179641424..5bfb8993cd1 100644 --- a/modules/debugging/bidInterceptor.js +++ b/modules/debugging/bidInterceptor.js @@ -3,10 +3,8 @@ import { deepClone, deepEqual, delayExecution, - prefixLog, mergeDeep } from '../../src/utils.js'; -const { logMessage, logWarn, logError } = prefixLog('DEBUG:'); /** * @typedef {Number|String|boolean|null|undefined} Scalar @@ -14,6 +12,7 @@ const { logMessage, logWarn, logError } = prefixLog('DEBUG:'); export function BidInterceptor(opts = {}) { ({setTimeout: this.setTimeout = window.setTimeout.bind(window)} = opts); + this.logger = opts.logger; this.rules = []; } @@ -22,10 +21,10 @@ Object.assign(BidInterceptor.prototype, { delay: 0 }, serializeConfig(ruleDefs) { - function isSerializable(ruleDef, i) { + const isSerializable = (ruleDef, i) => { const serializable = deepEqual(ruleDef, JSON.parse(JSON.stringify(ruleDef)), {checkTypes: true}); if (!serializable && !deepAccess(ruleDef, 'options.suppressWarnings')) { - logWarn(`Bid interceptor rule definition #${i + 1} is not serializable and will be lost after a refresh. Rule definition: `, ruleDef); + this.logger.logWarn(`Bid interceptor rule definition #${i + 1} is not serializable and will be lost after a refresh. Rule definition: `, ruleDef); } return serializable; } @@ -79,7 +78,7 @@ Object.assign(BidInterceptor.prototype, { return matchDef; } if (typeof matchDef !== 'object') { - logError(`Invalid 'when' definition for debug bid interceptor (in rule #${ruleNo})`); + this.logger.logError(`Invalid 'when' definition for debug bid interceptor (in rule #${ruleNo})`); return () => false; } function matches(candidate, {ref = matchDef, args = []}) { @@ -119,11 +118,11 @@ Object.assign(BidInterceptor.prototype, { if (typeof replDef === 'function') { replFn = ({args}) => replDef(...args); } else if (typeof replDef !== 'object') { - logError(`Invalid 'then' definition for debug bid interceptor (in rule #${ruleNo})`); + this.logger.logError(`Invalid 'then' definition for debug bid interceptor (in rule #${ruleNo})`); replFn = () => ({}); } else { replFn = ({args, ref = replDef}) => { - const result = {}; + const result = Array.isArray(ref) ? [] : {}; Object.entries(ref).forEach(([key, val]) => { if (typeof val === 'function') { result[key] = val(...args); @@ -213,7 +212,7 @@ Object.assign(BidInterceptor.prototype, { matches.forEach((match) => { const mockResponse = match.rule.replace(match.bid, bidRequest); const delay = match.rule.options.delay; - logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response:`, match.bid, mockResponse) + this.logger.logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response:`, match.bid, mockResponse) this.setTimeout(() => { addBid(mockResponse, match.bid); callDone(); diff --git a/modules/debugging/debugging.js b/modules/debugging/debugging.js new file mode 100644 index 00000000000..bf16eaf85a6 --- /dev/null +++ b/modules/debugging/debugging.js @@ -0,0 +1,109 @@ +import {deepClone, delayExecution} from '../../src/utils.js'; +import {BidInterceptor} from './bidInterceptor.js'; +import {makePbsInterceptor} from './pbsInterceptor.js'; +import {addHooks, removeHooks} from './legacy.js'; + +const interceptorHooks = []; +let bidInterceptor; +let enabled = false; + +function enableDebugging(debugConfig, {fromSession = false, config, hook, logger}) { + config.setConfig({debug: true}); + bidInterceptor.updateConfig(debugConfig); + resetHooks(true); + // also enable "legacy" overrides + removeHooks({hook}); + addHooks(debugConfig, {hook, logger}); + if (!enabled) { + enabled = true; + logger.logMessage(`Debug overrides enabled${fromSession ? ' from session' : ''}`); + } +} + +export function disableDebugging({hook, logger}) { + bidInterceptor.updateConfig(({})); + resetHooks(false); + // also disable "legacy" overrides + removeHooks({hook}); + if (enabled) { + enabled = false; + logger.logMessage('Debug overrides disabled'); + } +} + +function saveDebuggingConfig(debugConfig, {sessionStorage = window.sessionStorage, DEBUG_KEY} = {}) { + if (!debugConfig.enabled) { + try { + sessionStorage.removeItem(DEBUG_KEY); + } catch (e) { + } + } else { + if (debugConfig.intercept) { + debugConfig = deepClone(debugConfig); + debugConfig.intercept = bidInterceptor.serializeConfig(debugConfig.intercept); + } + try { + sessionStorage.setItem(DEBUG_KEY, JSON.stringify(debugConfig)); + } catch (e) { + } + } +} + +export function getConfig(debugging, {sessionStorage = window.sessionStorage, DEBUG_KEY, config, hook, logger} = {}) { + if (debugging == null) return; + saveDebuggingConfig(debugging, {sessionStorage, DEBUG_KEY}); + if (!debugging.enabled) { + disableDebugging({hook, logger}); + } else { + enableDebugging(debugging, {config, hook, logger}); + } +} + +export function sessionLoader({DEBUG_KEY, storage, config, hook, logger}) { + let overrides; + try { + storage = storage || window.sessionStorage; + overrides = JSON.parse(storage.getItem(DEBUG_KEY)); + } catch (e) { + } + if (overrides) { + enableDebugging(overrides, {fromSession: true, config, hook, logger}); + } +} + +function resetHooks(enable) { + interceptorHooks.forEach(([getHookFn, interceptor]) => { + getHookFn().getHooks({hook: interceptor}).remove(); + }); + if (enable) { + interceptorHooks.forEach(([getHookFn, interceptor]) => { + getHookFn().before(interceptor); + }); + } +} + +function registerBidInterceptor(getHookFn, interceptor) { + const interceptBids = (...args) => bidInterceptor.intercept(...args); + interceptorHooks.push([getHookFn, function (next, ...args) { + interceptor(next, interceptBids, ...args); + }]); +} + +export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, cbs) { + const done = delayExecution(cbs.onCompletion, 2); + ({bids, bidRequest} = interceptBids({bids, bidRequest, addBid: cbs.onBid, done})); + if (bids.length === 0) { + done(); + } else { + next(spec, bids, bidRequest, ajax, wrapCallback, {...cbs, onCompletion: done}); + } +} + +export function install({DEBUG_KEY, config, hook, createBid, logger}) { + bidInterceptor = new BidInterceptor({logger}); + const pbsBidInterceptor = makePbsInterceptor({createBid}); + registerBidInterceptor(() => hook.get('processBidderRequests'), bidderBidInterceptor); + registerBidInterceptor(() => hook.get('processPBSRequest'), pbsBidInterceptor); + sessionLoader({DEBUG_KEY, config, hook, logger}); + config.getConfig('debugging', ({debugging}) => getConfig(debugging, {DEBUG_KEY, config, hook, logger}), {init: true}); +} diff --git a/modules/debugging/index.js b/modules/debugging/index.js index 72692c3fc98..424200b2029 100644 --- a/modules/debugging/index.js +++ b/modules/debugging/index.js @@ -1,62 +1,8 @@ -import {deepClone, delayExecution} from '../../src/utils.js'; -import {processBidderRequests} from '../../src/adapters/bidderFactory.js'; -import {BidInterceptor} from './bidInterceptor.js'; +import {config} from '../../src/config.js'; import {hook} from '../../src/hook.js'; -import {pbsBidInterceptor} from './pbsInterceptor.js'; -import { - onDisableOverrides, - onEnableOverrides, - saveDebuggingConfig -} from '../../src/debugging.js'; +import {install} from './debugging.js'; +import {prefixLog} from '../../src/utils.js'; +import {createBid} from '../../src/bidfactory.js'; +import {DEBUG_KEY} from '../../src/debugging.js'; -const interceptorHooks = []; -const bidInterceptor = new BidInterceptor(); - -saveDebuggingConfig.before(function (next, debugConfig, ...args) { - if (debugConfig.intercept) { - debugConfig = deepClone(debugConfig); - debugConfig.intercept = bidInterceptor.serializeConfig(debugConfig.intercept); - } - next(debugConfig, ...args); -}); - -function resetHooks(enable) { - interceptorHooks.forEach(([getHookFn, interceptor]) => { - getHookFn().getHooks({hook: interceptor}).remove(); - }); - if (enable) { - interceptorHooks.forEach(([getHookFn, interceptor]) => { - getHookFn().before(interceptor); - }) - } -} - -onEnableOverrides.push((overrides) => { - bidInterceptor.updateConfig(overrides); - resetHooks(true); -}); - -onDisableOverrides.push(() => { - bidInterceptor.updateConfig({}); - resetHooks(false); -}) - -function registerBidInterceptor(getHookFn, interceptor) { - const interceptBids = (...args) => bidInterceptor.intercept(...args); - interceptorHooks.push([getHookFn, function (next, ...args) { - interceptor(next, interceptBids, ...args) - }]); -} - -export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, cbs) { - const done = delayExecution(cbs.onCompletion, 2); - ({bids, bidRequest} = interceptBids({bids, bidRequest, addBid: cbs.onBid, done})); - if (bids.length === 0) { - done(); - } else { - next(spec, bids, bidRequest, ajax, wrapCallback, {...cbs, onCompletion: done}); - } -} - -registerBidInterceptor(() => processBidderRequests, bidderBidInterceptor); -registerBidInterceptor(() => hook.get('processPBSRequest'), pbsBidInterceptor); +install({DEBUG_KEY, config, hook, createBid, logger: prefixLog('DEBUG:')}); diff --git a/modules/debugging/legacy.js b/modules/debugging/legacy.js new file mode 100644 index 00000000000..15b05cded64 --- /dev/null +++ b/modules/debugging/legacy.js @@ -0,0 +1,100 @@ +export let addBidResponseBound; +export let addBidderRequestsBound; + +export function addHooks(overrides, {hook, logger}) { + addBidResponseBound = addBidResponseHook.bind({overrides, logger}); + hook.get('addBidResponse').before(addBidResponseBound, 5); + + addBidderRequestsBound = addBidderRequestsHook.bind({overrides, logger}); + hook.get('addBidderRequests').before(addBidderRequestsBound, 5); +} + +export function removeHooks({hook}) { + hook.get('addBidResponse').getHooks({hook: addBidResponseBound}).remove(); + hook.get('addBidderRequests').getHooks({hook: addBidderRequestsBound}).remove(); +} + +/** + * @param {{bidder:string, adUnitCode:string}} overrideObj + * @param {string} bidderCode + * @param {string} adUnitCode + * @returns {boolean} + */ +export function bidExcluded(overrideObj, bidderCode, adUnitCode) { + if (overrideObj.bidder && overrideObj.bidder !== bidderCode) { + return true; + } + if (overrideObj.adUnitCode && overrideObj.adUnitCode !== adUnitCode) { + return true; + } + return false; +} + +/** + * @param {string[]} bidders + * @param {string} bidderCode + * @returns {boolean} + */ +export function bidderExcluded(bidders, bidderCode) { + return (Array.isArray(bidders) && bidders.indexOf(bidderCode) === -1); +} + +/** + * @param {Object} overrideObj + * @param {Object} bidObj + * @param {Object} bidType + * @returns {Object} bidObj with overridden properties + */ +export function applyBidOverrides(overrideObj, bidObj, bidType, logger) { + return Object.keys(overrideObj).filter(key => (['adUnitCode', 'bidder'].indexOf(key) === -1)).reduce(function(result, key) { + logger.logMessage(`bidder overrides changed '${result.adUnitCode}/${result.bidderCode}' ${bidType}.${key} from '${result[key]}.js' to '${overrideObj[key]}'`); + result[key] = overrideObj[key]; + result.isDebug = true; + return result; + }, bidObj); +} + +export function addBidResponseHook(next, adUnitCode, bid) { + const {overrides, logger} = this; + + if (bidderExcluded(overrides.bidders, bid.bidderCode)) { + logger.logWarn(`bidder '${bid.bidderCode}' excluded from auction by bidder overrides`); + return; + } + + if (Array.isArray(overrides.bids)) { + overrides.bids.forEach(function(overrideBid) { + if (!bidExcluded(overrideBid, bid.bidderCode, adUnitCode)) { + applyBidOverrides(overrideBid, bid, 'bidder', logger); + } + }); + } + + next(adUnitCode, bid); +} + +export function addBidderRequestsHook(next, bidderRequests) { + const {overrides, logger} = this; + + const includedBidderRequests = bidderRequests.filter(function (bidderRequest) { + if (bidderExcluded(overrides.bidders, bidderRequest.bidderCode)) { + logger.logWarn(`bidRequest '${bidderRequest.bidderCode}' excluded from auction by bidder overrides`); + return false; + } + return true; + }); + + if (Array.isArray(overrides.bidRequests)) { + includedBidderRequests.forEach(function(bidderRequest) { + overrides.bidRequests.forEach(function(overrideBid) { + bidderRequest.bids.forEach(function(bid) { + if (!bidExcluded(overrideBid, bidderRequest.bidderCode, bid.adUnitCode)) { + applyBidOverrides(overrideBid, bid, 'bidRequest', logger); + } + }); + }); + }); + } + + next(includedBidderRequests); +} diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js index c8de1ed9753..1ca13eb4927 100644 --- a/modules/debugging/pbsInterceptor.js +++ b/modules/debugging/pbsInterceptor.js @@ -1,38 +1,39 @@ import {deepClone, delayExecution} from '../../src/utils.js'; -import {createBid} from '../../src/bidfactory.js'; -import {default as CONSTANTS} from '../../src/constants.json'; +import CONSTANTS from '../../src/constants.json'; -export function pbsBidInterceptor (next, interceptBids, s2sBidRequest, bidRequests, ajax, { - onResponse, - onError, - onBid -}) { - let responseArgs; - const done = delayExecution(() => onResponse(...responseArgs), bidRequests.length + 1) - function signalResponse(...args) { - responseArgs = args; - done(); - } - function addBid(bid, bidRequest) { - onBid({ - adUnit: bidRequest.adUnitCode, - bid: Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid) - }) - } - bidRequests = bidRequests - .map((req) => interceptBids({bidRequest: req, addBid, done}).bidRequest) - .filter((req) => req.bids.length > 0) +export function makePbsInterceptor({createBid}) { + return function pbsBidInterceptor(next, interceptBids, s2sBidRequest, bidRequests, ajax, { + onResponse, + onError, + onBid + }) { + let responseArgs; + const done = delayExecution(() => onResponse(...responseArgs), bidRequests.length + 1) + function signalResponse(...args) { + responseArgs = args; + done(); + } + function addBid(bid, bidRequest) { + onBid({ + adUnit: bidRequest.adUnitCode, + bid: Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid) + }) + } + bidRequests = bidRequests + .map((req) => interceptBids({bidRequest: req, addBid, done}).bidRequest) + .filter((req) => req.bids.length > 0) - if (bidRequests.length > 0) { - const bidIds = new Set(); - bidRequests.forEach((req) => req.bids.forEach((bid) => bidIds.add(bid.bidId))); - s2sBidRequest = deepClone(s2sBidRequest); - s2sBidRequest.ad_units.forEach((unit) => { - unit.bids = unit.bids.filter((bid) => bidIds.has(bid.bid_id)); - }) - s2sBidRequest.ad_units = s2sBidRequest.ad_units.filter((unit) => unit.bids.length > 0); - next(s2sBidRequest, bidRequests, ajax, {onResponse: signalResponse, onError, onBid}); - } else { - signalResponse(true, []); + if (bidRequests.length > 0) { + const bidIds = new Set(); + bidRequests.forEach((req) => req.bids.forEach((bid) => bidIds.add(bid.bidId))); + s2sBidRequest = deepClone(s2sBidRequest); + s2sBidRequest.ad_units.forEach((unit) => { + unit.bids = unit.bids.filter((bid) => bidIds.has(bid.bid_id)); + }) + s2sBidRequest.ad_units = s2sBidRequest.ad_units.filter((unit) => unit.bids.length > 0); + next(s2sBidRequest, bidRequests, ajax, {onResponse: signalResponse, onError, onBid}); + } else { + signalResponse(true, []); + } } } diff --git a/modules/debugging/standalone.js b/modules/debugging/standalone.js new file mode 100644 index 00000000000..b3b539f5aa2 --- /dev/null +++ b/modules/debugging/standalone.js @@ -0,0 +1,7 @@ +import {install} from './debugging.js'; + +window._pbjsGlobals.forEach((name) => { + if (window[name] && window[name]._installDebugging === true) { + window[name]._installDebugging = install; + } +}) diff --git a/modules/decenteradsBidAdapter.md b/modules/decenteradsBidAdapter.md deleted file mode 100644 index 04260a9da58..00000000000 --- a/modules/decenteradsBidAdapter.md +++ /dev/null @@ -1,27 +0,0 @@ -# Overview - -``` -Module Name: DecenterAds Bidder Adapter -Module Type: Bidder Adapter -Maintainer: publishers@decenterads.com -``` - -# Description - -Module that connects to DecenterAds' demand sources - -# Test Parameters -``` - var adUnits = [{ - code: 'placementId_0', - sizes: [[300, 250]], - bids: [{ - bidder: 'decenterads', - params: { - placementId: 0, - traffic: 'banner' - } - }] - } - ]; -``` diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index 94167b92bb0..e062686b320 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -262,21 +262,13 @@ function buildBanner(bid) { function buildSite(bidderRequest) { let site = {}; - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - site.page = bidderRequest.refererInfo.referer; - site.domain = getDomain(bidderRequest.refererInfo.referer); + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + site.page = bidderRequest.refererInfo.page; + site.domain = bidderRequest.refererInfo.domain; } return site; } -function getDomain(referer) { - if (referer) { - let domainA = document.createElement('a'); - domainA.href = referer; - return domainA.hostname; - } -} - function buildDevice() { return { ua: navigator.userAgent, diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js index 33df5bd252e..e40ec58461c 100644 --- a/modules/deltaprojectsBidAdapter.js +++ b/modules/deltaprojectsBidAdapter.js @@ -1,8 +1,6 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { - _each, _map, isFn, isNumber, createTrackPixelHtml, deepAccess, parseUrl, logWarn, logError -} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {_each, _map, createTrackPixelHtml, deepAccess, isFn, isNumber, logError, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; export const BIDDER_CODE = 'deltaprojects'; @@ -32,14 +30,13 @@ function buildRequests(validBidRequests, bidderRequest) { const id = bidderRequest.auctionId; // -- build site - const loc = parseUrl(bidderRequest.refererInfo.referer); const publisherId = setOnAny(validBidRequests, 'params.publisherId'); const siteId = setOnAny(validBidRequests, 'params.siteId'); const site = { id: siteId, - domain: loc.hostname, - page: loc.href, - ref: loc.href, + domain: bidderRequest.refererInfo.domain, + page: bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref, publisher: { id: publisherId }, }; diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 37f038d2a67..072715b4bd6 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -91,7 +91,7 @@ export function buildDfpVideoUrl(options) { }; const urlSearchComponent = urlComponents.search; - const urlSzParam = urlSearchComponent && urlSearchComponent.sz + const urlSzParam = urlSearchComponent && urlSearchComponent.sz; if (urlSzParam) { derivedParams.sz = urlSzParam + '|' + derivedParams.sz; } @@ -186,7 +186,7 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { let initialValue = { [adpodUtils.TARGETING_KEY_PB_CAT_DUR]: undefined, [adpodUtils.TARGETING_KEY_CACHE_ID]: undefined - } + }; let customParams = {}; if (targeting[code]) { customParams = targeting[code].reduce((acc, curValue) => { diff --git a/modules/dgadsBidAdapter.md b/modules/dgadsBidAdapter.md deleted file mode 100644 index b1544007a43..00000000000 --- a/modules/dgadsBidAdapter.md +++ /dev/null @@ -1,65 +0,0 @@ -# Overview - -``` -Module Name: Digital Garage Ads Platform Bidder Adapter -Module Type: Bidder Adapter -Maintainer:dgads-support@garage.co.jp -``` - -# Description - -Connect to Digital Garage Ads Platform for bids. -This adapter supports Banner and Native. - -# Test Parameters -``` - var adUnits = [ - // Banner - { - code: 'banner-div', - sizes: [[300, 250]], - bids: [{ - bidder: 'dgads', - mediaTypes: 'banner', - params: { - location_id: '1', - site_id: '1' - } - }] - }, - // Native - { - code: 'native-div', - sizes: [[300, 250]], - mediaTypes: { - native: { - title: { - required: true, - len: 25 - }, - body: { - required: true, - len: 140 - }, - sponsoredBy: { - required: true, - len: 40 - }, - image: { - required: true - }, - clickUrl: { - required: true - }, - } - }, - bids: [{ - bidder: 'dgads', - params: { - location_id: '10', - site_id: '1' - } - }] - }, - ]; -``` diff --git a/modules/dgkeywordRtdProvider.js b/modules/dgkeywordRtdProvider.js index 26a8257077a..e2a29375f25 100644 --- a/modules/dgkeywordRtdProvider.js +++ b/modules/dgkeywordRtdProvider.js @@ -7,7 +7,7 @@ * @requires module:modules/realTimeData */ -import { logMessage, deepSetValue, logError, logInfo } from '../src/utils.js'; +import {logMessage, deepSetValue, logError, logInfo, mergeDeep} from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getGlobal } from '../src/prebidGlobal.js'; @@ -25,6 +25,15 @@ export function getDgKeywordsAndSet(reqBidsConfigObj, callback, moduleConfig, us const timeout = (moduleConfig && moduleConfig.params && moduleConfig.params.timeout && Number(moduleConfig.params.timeout) > 0) ? Number(moduleConfig.params.timeout) : PROFILE_TIMEOUT_MS; const url = (moduleConfig && moduleConfig.params && moduleConfig.params.url) ? moduleConfig.params.url : URL + encodeURIComponent(window.location.href); const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + callback = (function(cb) { + let done = false; + return function () { + if (!done) { + done = true; + return cb.apply(this, arguments); + } + } + })(callback); let isFinish = false; logMessage('[dgkeyword sub module]', adUnits, timeout); let setKeywordTargetBidders = getTargetBidderOfDgKeywords(adUnits); @@ -48,7 +57,7 @@ export function getDgKeywordsAndSet(reqBidsConfigObj, callback, moduleConfig, us keywords['opectx'] = res['t']; } if (Object.keys(keywords).length > 0) { - const targetBidKeys = {} + const targetBidKeys = {}; for (let bid of setKeywordTargetBidders) { // set keywords to params bid.params.keywords = keywords; @@ -62,8 +71,7 @@ export function getDgKeywordsAndSet(reqBidsConfigObj, callback, moduleConfig, us let addOrtb2 = {}; deepSetValue(addOrtb2, 'site.keywords', keywords); deepSetValue(addOrtb2, 'user.keywords', keywords); - const ortb2 = {ortb2: addOrtb2}; - reqBidsConfigObj.setBidderConfig({ bidders: Object.keys(targetBidKeys), config: ortb2 }); + mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, Object.fromEntries(Object.keys(targetBidKeys).map(bidder => [bidder, addOrtb2]))); } } } diff --git a/modules/dianomiBidAdapter.js b/modules/dianomiBidAdapter.js new file mode 100644 index 00000000000..c64584f3ae8 --- /dev/null +++ b/modules/dianomiBidAdapter.js @@ -0,0 +1,372 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { NATIVE, BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + mergeDeep, + _map, + deepAccess, + parseSizesInput, + deepSetValue, + formatQS, +} from '../src/utils.js'; +import { config } from '../src/config.js'; +import { Renderer } from '../src/Renderer.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +const { getConfig } = config; + +const BIDDER_CODE = 'dianomi'; +const GVLID = 885; +const BIDDER_ALIAS = [{ code: 'dia', gvlid: GVLID }]; +const NATIVE_ASSET_IDS = { + 0: 'title', + 2: 'icon', + 3: 'image', + 5: 'sponsoredBy', + 4: 'body', + 1: 'cta', +}; +const NATIVE_PARAMS = { + title: { + id: 0, + name: 'title', + }, + icon: { + id: 2, + type: 1, + name: 'img', + }, + image: { + id: 3, + type: 3, + name: 'img', + }, + sponsoredBy: { + id: 5, + name: 'data', + type: 1, + }, + body: { + id: 4, + name: 'data', + type: 2, + }, + cta: { + id: 1, + type: 12, + name: 'data', + }, +}; +let endpoint = 'www-prebid.dianomi.com'; + +const OUTSTREAM_RENDERER_URL = (hostname) => `https://${hostname}/prebid/outstream/renderer.js`; + +export const spec = { + code: BIDDER_CODE, + aliases: BIDDER_ALIAS, + gvlid: GVLID, + supportedMediaTypes: [NATIVE, BANNER, VIDEO], + isBidRequestValid: (bid) => { + const params = bid.params || {}; + const { smartadId } = params; + return !!smartadId; + }, + buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let app, site; + + const commonFpd = bidderRequest.ortb2 || {}; + let { user } = commonFpd; + + if (typeof getConfig('app') === 'object') { + app = getConfig('app') || {}; + if (commonFpd.app) { + mergeDeep(app, commonFpd.app); + } + } else { + site = getConfig('site') || {}; + if (commonFpd.site) { + mergeDeep(site, commonFpd.site); + } + + if (!site.page) { + site.page = bidderRequest.refererInfo.page; + } + } + + const device = getConfig('device') || {}; + device.w = device.w || window.innerWidth; + device.h = device.h || window.innerHeight; + device.ua = device.ua || navigator.userAgent; + + const paramsEndpoint = setOnAny(validBidRequests, 'params.endpoint'); + + if (paramsEndpoint) { + endpoint = paramsEndpoint; + } + + const pt = + setOnAny(validBidRequests, 'params.pt') || + setOnAny(validBidRequests, 'params.priceType') || + 'net'; + const tid = validBidRequests[0].transactionId; + const currency = getConfig('currency.adServerCurrency'); + const cur = currency && [currency]; + const eids = setOnAny(validBidRequests, 'userIdAsEids'); + const schain = setOnAny(validBidRequests, 'schain'); + + const imp = validBidRequests.map((bid, id) => { + bid.netRevenue = pt; + + const floorInfo = bid.getFloor + ? bid.getFloor({ + currency: currency || 'USD', + }) + : {}; + const bidfloor = floorInfo.floor; + const bidfloorcur = floorInfo.currency; + const { smartadId } = bid.params; + + const imp = { + id: id + 1, + tagid: smartadId, + bidfloor, + bidfloorcur, + ext: { + bidder: { + smartadId: smartadId, + }, + }, + }; + + const assets = _map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + asset.id = props.id; + let wmin, hmin, w, h; + let aRatios = bidParams.aspect_ratios; + + if (aRatios && aRatios[0]) { + aRatios = aRatios[0]; + wmin = aRatios.min_width || 0; + hmin = ((aRatios.ratio_height * wmin) / aRatios.ratio_width) | 0; + } + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + w = sizes[0]; + h = sizes[1]; + } + + asset[props.name] = { + len: bidParams.len, + type: props.type, + wmin, + hmin, + w, + h, + }; + + return asset; + } + }).filter(Boolean); + + if (assets.length) { + imp.native = { + assets, + }; + } + + const bannerParams = deepAccess(bid, 'mediaTypes.banner'); + + if (bannerParams && bannerParams.sizes) { + const sizes = parseSizesInput(bannerParams.sizes); + const format = sizes.map((size) => { + const [width, height] = size.split('x'); + const w = parseInt(width, 10); + const h = parseInt(height, 10); + return { w, h }; + }); + + imp.banner = { + format, + }; + } + + const videoParams = deepAccess(bid, 'mediaTypes.video'); + if (videoParams) { + imp.video = videoParams; + } + + return imp; + }); + + const request = { + id: bidderRequest.auctionId, + site, + app, + user, + device, + source: { tid, fd: 1 }, + ext: { pt }, + cur, + imp, + }; + + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); + } + + if (bidderRequest.uspConsent) { + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (eids) { + deepSetValue(request, 'user.ext.eids', eids); + } + + if (schain) { + deepSetValue(request, 'source.ext.schain', schain); + } + + return { + method: 'POST', + url: 'https://' + endpoint + '/cgi-bin/smartads_prebid.pl', + data: JSON.stringify(request), + bids: validBidRequests, + }; + }, + interpretResponse: function (serverResponse, { bids }) { + if (!serverResponse.body || serverResponse?.body?.nbr) { + return; + } + const { seatbid, cur } = serverResponse.body; + + const bidResponses = flatten(seatbid.map((seat) => seat.bid)).reduce((result, bid) => { + result[bid.impid - 1] = bid; + return result; + }, []); + + return bids + .map((bid, id) => { + const bidResponse = bidResponses[id]; + if (bidResponse) { + const mediaType = deepAccess(bidResponse, 'ext.prebid.type'); + const result = { + requestId: bid.bidId, + cpm: bidResponse.price, + creativeId: bidResponse.crid, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: cur, + mediaType, + width: bidResponse.w, + height: bidResponse.h, + dealId: bidResponse.dealid, + meta: { + mediaType, + advertiserDomains: bidResponse.adomain, + }, + }; + + if (bidResponse.native) { + result.native = parseNative(bidResponse); + } else { + result[mediaType === VIDEO ? 'vastXml' : 'ad'] = bidResponse.adm; + } + + if ( + !bid.renderer && + mediaType === VIDEO && + deepAccess(bid, 'mediaTypes.video.context') === 'outstream' + ) { + result.renderer = Renderer.install({ + id: bid.bidId, + url: OUTSTREAM_RENDERER_URL(endpoint), + adUnitCode: bid.adUnitCode, + }); + result.renderer.setRender(renderer); + } + + return result; + } + }) + .filter(Boolean); + }, + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { + if (syncOptions.iframeEnabled) { + // data is only assigned if params are available to pass to syncEndpoint + const params = {}; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params['gdpr'] = Number(gdprConsent.gdprApplies); + } + if (typeof gdprConsent.consentString === 'string') { + params['gdpr_consent'] = gdprConsent.consentString; + } + } + + if (uspConsent) { + params['us_privacy'] = encodeURIComponent(uspConsent); + } + + return { + type: 'iframe', + url: `https://${endpoint}/prebid/usersync/index.html?${formatQS(params)}`, + }; + } + }, +}; + +registerBidder(spec); + +function parseNative(bid) { + const { assets, link, imptrackers, jstracker } = bid.native; + const result = { + clickUrl: link.url, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined, + javascriptTrackers: jstracker ? [jstracker] : undefined, + }; + assets.forEach((asset) => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + if (content) { + result[kind] = content.text || + content.value || { + url: content.url, + width: content.w, + height: content.h, + }; + } + }); + + return result; +} + +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +function flatten(arr) { + return [].concat(...arr); +} + +function renderer(bid) { + bid.renderer.push(() => { + window.Dianomi.renderOutstream(bid); + }); +} diff --git a/modules/dianomiBidAdapter.md b/modules/dianomiBidAdapter.md new file mode 100644 index 00000000000..e0b749e0765 --- /dev/null +++ b/modules/dianomiBidAdapter.md @@ -0,0 +1,73 @@ +# Overview + +``` +Module Name: Dianomi Bidder Adapter +Module Type: Bidder Adapter +Maintainer: eng@dianomi.com +``` + +# Description + +Module that connects to Dianomi's demand sources. Both Native and Banner formats supported. Using oRTB standard. + +# Test Parameters + +```js + var adUnits = [ + { + code: 'test-div-1', + mediaTypes: { + native: { + rendererUrl: "https://dev.dianomi.com/chris/prebid/dianomiRenderer.js", + image: { + required: true, + sizes: [360, 360] + }, + title: { + required: true, + len: 800 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: false + }, + icon: { + required: false, + sizes: [75, 75] + }, + } + }, + bids: [ + { + bidder: "dianomi", + params: { + smartadId: 12345 // required, provided by Account Manager + } + } + ] + },{ + code: 'test-div-2', + mediaTypes: { + banner: { + sizes: [750, 650], // a below-article size + } + }, + bids: [ + { + bidder: "dianomi", + params: { + smartadId: 23456, // required provided by Account Manager + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js new file mode 100644 index 00000000000..4f22a41cf9f --- /dev/null +++ b/modules/discoveryBidAdapter.js @@ -0,0 +1,467 @@ +import * as utils from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'discovery'; +const ENDPOINT_URL = 'https://rtb-jp.mediago.io/api/bid?tn='; +const TIME_TO_LIVE = 500; +const storage = getStorageManager(); +let globals = {}; +let itemMaps = {}; +const MEDIATYPE = [BANNER, NATIVE]; + +/* ----- _ss_pp_id:start ------ */ +const COOKIE_KEY_MGUID = '_ss_pp_id'; + +const NATIVERET = { + id: 'id', + bidfloor: 0, + // TODO Dynamic parameters + native: { + ver: '1.2', + plcmtcnt: 1, + assets: [ + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + wmin: 300, + h: 174, + hmin: 174, + }, + }, + { + id: 2, + required: 1, + title: { + len: 75, + }, + } + ], + plcmttype: 1, + privacy: 1, + eventtrackers: [ + { + event: 1, + methods: [1, 2], + }, + ], + }, + ext: {}, +}; + +/** + * 获取用户id + * @return {string} + */ +const getUserID = () => { + const i = storage.getCookie(COOKIE_KEY_MGUID); + + if (i === null) { + const uuid = utils.generateUUID(); + storage.setCookie(COOKIE_KEY_MGUID, uuid); + return uuid; + } + return i; +}; + +/* ----- _ss_pp_id:end ------ */ + +/** + * get object key -> value + * @param {Object} obj 对象 + * @param {...string} keys 键名 + * @return {any} + */ +function getKv(obj, ...keys) { + let o = obj; + + for (let key of keys) { + // console.log(key, o); + if (o && o[key]) { + o = o[key]; + } else { + return ''; + } + } + return o; +} + +/** + * get device + * @return {boolean} + */ +function getDevice() { + let check = false; + (function (a) { + let reg1 = new RegExp( + [ + '(android|bbd+|meego)', + '.+mobile|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)', + '|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone', + '|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up.(browser|link)|vodafone|wap', + '|windows ce|xda|xiino|android|ipad|playbook|silk', + ].join(''), + 'i' + ); + let reg2 = new RegExp( + [ + '1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)', + '|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )', + '|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell', + '|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)', + '|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene', + '|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c', + '|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom', + '|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|kyo(c|k)', + '|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)', + '|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]', + '|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)', + '|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio', + '|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms', + '|ny|va)|sc(01|h-|oo|p-)|sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al', + '|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)', + '|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(.b|g1|si)|utst|', + 'v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)', + '|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-', + '|your|zeto|zte-', + ].join(''), + 'i' + ); + if (reg1.test(a) || reg2.test(a.substr(0, 4))) { + check = true; + } + })(navigator.userAgent || navigator.vendor || window.opera); + return check; +} + +/** + * get BidFloor + * @param {*} bid + * @param {*} mediaType + * @param {*} sizes + * @returns + */ +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return utils.deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0; + } +} + +/** + * get sizes for rtb + * @param {Array|Object} requestSizes + * @return {Object} + */ +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if ( + utils.isArray(requestSizes) && + requestSizes.length === 2 && + !utils.isArray(requestSizes[0]) + ) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +// Support sizes +const popInAdSize = [ + { w: 300, h: 250 }, + { w: 300, h: 600 }, + { w: 728, h: 90 }, + { w: 970, h: 250 }, + { w: 320, h: 50 }, + { w: 160, h: 600 }, + { w: 320, h: 180 }, + { w: 320, h: 100 }, + { w: 336, h: 280 }, +]; + +/** + * get aditem setting + * @param {Array} validBidRequests an an array of bids + * @param {Object} bidderRequest The master bidRequest object + * @return {Object} + */ +function getItems(validBidRequests, bidderRequest) { + let items = []; + items = validBidRequests.map((req, i) => { + let ret = {}; + // eslint-disable-next-line no-debugger + let mediaTypes = getKv(req, 'mediaTypes'); + + const bidFloor = getBidFloor(req); + let id = '' + (i + 1); + + if (mediaTypes.native) { + ret = {...NATIVERET, ...{id, bidFloor}} + } + // banner + if (mediaTypes.banner) { + let sizes = transformSizes(getKv(req, 'sizes')); + let matchSize; + + for (let size of sizes) { + matchSize = popInAdSize.find( + (item) => size.width === item.w && size.height === item.h + ); + if (matchSize) { + break; + } + } + if (!matchSize) { + return {}; + } + ret = { + id: id, + bidfloor: bidFloor, + banner: { + h: matchSize.h, + w: matchSize.w, + pos: 1, + }, + ext: {}, + }; + } + itemMaps[id] = { + req, + ret, + }; + return ret; + }); + return items; +} + +/** + * get rtb qequest params + * + * @param {Array} validBidRequests an an array of bids + * @param {Object} bidderRequest The master bidRequest object + * @return {Object} + */ +function getParam(validBidRequests, bidderRequest) { + const pubcid = utils.deepAccess(validBidRequests[0], 'crumbs.pubcid'); + let isMobile = getDevice() ? 1 : 0; + let auctionId = getKv(bidderRequest, 'auctionId'); + let items = getItems(validBidRequests, bidderRequest); + + const location = utils.deepAccess(bidderRequest, 'refererInfo.referer'); + + const timeout = bidderRequest.timeout || 2000; + + if (items && items.length) { + let c = { + id: 'pp_hbjs_' + auctionId, + at: 1, + cur: ['USD'], + device: { + connectiontype: 0, + js: 1, + os: navigator.platform || '', + ua: navigator.userAgent, + language: /en/.test(navigator.language) ? 'en' : navigator.language, + }, + user: { + buyeruid: getUserID(), + id: pubcid, + }, + tmax: timeout, + site: { + name: globals['media'], + domain: globals['media'], + page: location, + ref: location, + mobile: isMobile, + cat: [], // todo + publisher: { + // todo + id: globals['media'], + name: globals['media'], + }, + }, + imp: items, + }; + return c; + } else { + return null; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: MEDIATYPE, + // aliases: ['ex'], // short code + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (bid.params.token) { + globals['token'] = bid.params.token; + } + if (bid.params.media) { + globals['media'] = bid.params.media; + } + return !!(bid.params.token && bid.params.media); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {Array} validBidRequests an an array of bids + * @param {Object} bidderRequest The master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let payload = getParam(validBidRequests, bidderRequest); + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL + globals['token'], + data: payloadString, + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bids = getKv(serverResponse, 'body', 'seatbid', 0, 'bid'); + const cur = getKv(serverResponse, 'body', 'cur'); + const bidResponses = []; + for (let bid of bids) { + let impid = getKv(bid, 'impid'); + if (itemMaps[impid]) { + let bidId = getKv(itemMaps[impid], 'req', 'bidId'); + const mediaType = getKv(bid, 'w') ? 'banner' : 'native'; + let bidResponse = { + requestId: bidId, + cpm: getKv(bid, 'price'), + creativeId: getKv(bid, 'cid'), + mediaType, + currency: cur, + netRevenue: true, + nurl: getKv(bid, 'nurl'), + ttl: TIME_TO_LIVE, + meta: { + advertiserDomains: getKv(bid, 'adomain') || [] + } + }; + if (mediaType === 'native') { + const adm = getKv(bid, 'adm'); + const admObj = JSON.parse(adm); + var native = {}; + admObj.assets.forEach((asset) => { + if (asset.title) { + native.title = asset.title.text; + } else if (asset.data) { + native.data = asset.data.value; + } else if (asset.img) { + switch (asset.img.type) { + case 1: + native.icon = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h, + }; + break; + default: + native.image = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h, + }; + break; + } + } + }); + if (admObj.link) { + if (admObj.link.url) { + native.clickUrl = admObj.link.url; + } + } + if (Array.isArray(admObj.eventtrackers)) { + native.impressionTrackers = []; + admObj.eventtrackers.forEach((tracker) => { + if (tracker.event !== 1) { + return; + } + switch (tracker.method) { + case 1: + native.impressionTrackers.push(tracker.url); + break; + // case 2: + // native.javascriptTrackers = ``; + // break; + } + }); + } + if (admObj.purl) { + native.purl = admObj.purl; + } + bidResponse['native'] = native; + } else { + bidResponse['width'] = getKv(bid, 'w'); + bidResponse['height'] = getKv(bid, 'h'); + bidResponse['ad'] = getKv(bid, 'adm'); + } + bidResponses.push(bidResponse); + } + } + + return bidResponses; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + onTimeout: function (data) { + utils.logError('DiscoveryDSP adapter timed out for the auction.'); + // TODO send request timeout to serve, the interface is not ready + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function (bid) { + if (bid['nurl']) { + utils.triggerPixel(bid['nurl']) + } + } +}; +registerBidder(spec); diff --git a/modules/discoveryBidAdapter.md b/modules/discoveryBidAdapter.md new file mode 100644 index 00000000000..6e7197863a5 --- /dev/null +++ b/modules/discoveryBidAdapter.md @@ -0,0 +1,60 @@ +# Overview + +``` +Module Name: DiscoveryDSP Bid Adapter +Module Type: Bidder Adapter +``` + +# Description + +Module that connects to popIn's demand sources + +The DiscoveryDSP Bidding adapter requires setup before beginning. Please contact us at + +# Test Parameters +``` + var adUnits = [ + // native + { + code: "test-div-1", + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + } + } + }, + bids: [ + { + bidder: "discovery", + params: { + token: "a1b067897e4ae093d1f94261e0ddc6c9", + media: 'test_media' // your media host + }, + }, + ], + }, + // banner + { + code: "test-div-2", + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + // Replace this object to test a new adapter! + bids: [ + { + bidder: "discovery", + params: { + token: "d0f4902b616cc5c38cbe0a08676d0ed9", + media: 'test_media' // your media host + }, + }, + ], + }, + ]; +``` diff --git a/modules/displayioBidAdapter.js b/modules/displayioBidAdapter.js index 55a2f4a8604..c3c6597dd1b 100644 --- a/modules/displayioBidAdapter.js +++ b/modules/displayioBidAdapter.js @@ -1,16 +1,16 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import {getWindowFromDocument, logWarn} from '../src/utils.js'; -const BIDDER_VERSION = '1.0.0'; +const ADAPTER_VERSION = '1.1.0'; const BIDDER_CODE = 'displayio'; -const GVLID = 999; const BID_TTL = 300; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const DEFAULT_CURRENCY = 'USD'; export const spec = { code: BIDDER_CODE, - gvlid: GVLID, supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function(bid) { return !!(bid.params && bid.params.placementId && bid.params.siteId && @@ -20,7 +20,7 @@ export const spec = { return bidRequests.map(bid => { let url = '//' + bid.params.adsSrvDomain + '/srv?method=getPlacement&app=' + bid.params.siteId + '&placement=' + bid.params.placementId; - const data = this._getPayload(bid, bidderRequest); + const data = getPayload(bid, bidderRequest); return { method: 'POST', headers: {'Content-Type': 'application/json;charset=utf-8'}, @@ -42,116 +42,112 @@ export const spec = { height: adData.h, netRevenue: true, ttl: BID_TTL, - creativeId: adData.adId || 0, - currency: DEFAULT_CURRENCY, + creativeId: adData.adId || 1, + currency: adData.cur || DEFAULT_CURRENCY, referrer: data.data.ref, - mediaType: ads[0].ad.subtype, + mediaType: ads[0].ad.subtype === 'videoVast' ? VIDEO : BANNER, ad: adData.markup, - placement: data.placement, + adUnitCode: data.adUnitCode, + renderURL: data.renderURL, adData: adData }; - if (bidResponse.vastUrl === 'videoVast') { - bidResponse.vastUrl = adData.videos[0].url + + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastUrl = adData.videos[0] && adData.videos[0].url + } + + if (bidResponse.renderURL) { + bidResponse.renderer = newRenderer(bidResponse); } bidResponses.push(bidResponse); } return bidResponses; - }, - _getPayload: function (bid, bidderRequest) { - const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; - const userSession = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => { - let r = Math.random() * 16 | 0; - let v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - const { params } = bid; - const { siteId, placementId } = params; - const { refererInfo, uspConsent, gdprConsent } = bidderRequest; - const mediation = {consent: '-1', gdpr: '-1'}; - if (gdprConsent) { - if (gdprConsent.consentString !== undefined) { - mediation.consent = gdprConsent.consentString; - } - if (gdprConsent.gdprApplies !== undefined) { - mediation.gdpr = gdprConsent.gdprApplies ? '1' : '0'; - } + } +}; + +function getPayload (bid, bidderRequest) { + const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; + const userSession = 'us_web_xxxxxxxxxxxx'.replace(/[x]/g, c => { + let r = Math.random() * 16 | 0; + let v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + const { params, adUnitCode, bidId } = bid; + const { siteId, placementId, renderURL, pageCategory, keywords } = params; + const { refererInfo, uspConsent, gdprConsent } = bidderRequest; + const mediation = {consent: '-1', gdpr: '-1'}; + if (gdprConsent && 'gdprApplies' in gdprConsent) { + if (gdprConsent.consentString !== undefined) { + mediation.consent = gdprConsent.consentString; } - const payload = { - userSession, + if (gdprConsent.gdprApplies !== undefined) { + mediation.gdpr = gdprConsent.gdprApplies ? '1' : '0'; + } + } + return { + userSession, + data: { + id: bidId, + action: 'getPlacement', + app: siteId, + placement: placementId, + adUnitCode, + renderURL, data: { - id: bid.bidId, - action: 'getPlacement', - app: siteId, - placement: placementId, - data: { - pagecat: params.pageCategory ? params.pageCategory.split(',').map(k => k.trim()) : [], - keywords: params.keywords ? params.keywords.split(',').map(k => k.trim()) : [], - lang_content: document.documentElement.lang, - lang: window.navigator.language, - domain: window.location.hostname, - page: window.location.href, - ref: refererInfo.referer, - userids: _getUserIDs(), - geo: '', - }, - complianceData: { - child: '-1', - us_privacy: uspConsent, - dnt: window.navigator.doNotTrack, - iabConsent: {}, - mediation: { - consent: mediation.consent, - gdpr: mediation.gdpr, - } - }, - integration: 'JS', - omidpn: 'Displayio', - mediationPlatform: 0, - prebidVersion: BIDDER_VERSION, - device: { - w: window.screen.width, - h: window.screen.height, - connection_type: connection ? connection.effectiveType : '', + pagecat: pageCategory ? pageCategory.split(',').map(k => k.trim()) : [], + keywords: keywords ? keywords.split(',').map(k => k.trim()) : [], + lang_content: document.documentElement.lang, + lang: window.navigator.language, + domain: refererInfo.domain, + page: refererInfo.page, + ref: refererInfo.referer, + userids: bid.userIdAsEids || {}, + geo: '', + }, + complianceData: { + child: '-1', + us_privacy: uspConsent, + dnt: window.doNotTrack === '1' || window.navigator.doNotTrack === '1' || false, + iabConsent: {}, + mediation: { + consent: mediation.consent, + gdpr: mediation.gdpr, } + }, + integration: 'JS', + omidpn: 'Displayio', + mediationPlatform: 0, + prebidVersion: ADAPTER_VERSION, + device: { + w: window.screen.width, + h: window.screen.height, + connection_type: connection ? connection.effectiveType : '', } } - if (navigator.permissions) { - navigator.permissions.query({ name: 'geolocation' }) - .then((result) => { - if (result.state === 'granted') { - payload.data.data.geo = _getGeoData(); - } - }); - } - return payload } -}; +} + +function newRenderer(bid) { + const renderer = Renderer.install({ + id: bid.requestId, + url: bid.renderURL, + adUnitCode: bid.adUnitCode + }); -function _getUserIDs () { - let ids = {}; try { - ids = window.owpbjs.getUserIdsAsEids(); - } catch (e) {} - return ids; + renderer.setRender(webisRender); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; } -async function _getGeoData () { - let geoData = null; - const getCurrentPosition = () => { - return new Promise((resolve, reject) => - navigator.geolocation.getCurrentPosition(resolve, reject) - ); - } - try { - const position = await getCurrentPosition(); - let {latitude, longitude, accuracy} = position.coords; - geoData = { - 'lat': latitude, - 'lng': longitude, - 'precision': accuracy - }; - } catch (e) {} - return geoData +function webisRender(bid, doc) { + bid.renderer.push(() => { + const win = getWindowFromDocument(doc) || window; + win.webis.init(bid.adData, bid.adUnitCode, bid.params); + }) } registerBidder(spec); diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js deleted file mode 100644 index f909a1f1329..00000000000 --- a/modules/districtmDMXBidAdapter.js +++ /dev/null @@ -1,433 +0,0 @@ -import { isArray, generateUUID, deepAccess, isStr } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; - -const BIDDER_CODE = 'districtmDMX'; - -const DMXURI = 'https://dmx.districtm.io/b/v1'; - -const GVLID = 144; -const VIDEO_MAPPING = { - playback_method: { - 'auto_play_sound_on': 1, - 'auto_play_sound_off': 2, - 'click_to_play': 3, - 'mouse_over': 4, - 'viewport_sound_on': 5, - 'viewport_sound_off': 6 - } -}; -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - aliases: ['dmx'], - supportedFormat: [BANNER, VIDEO], - supportedMediaTypes: [VIDEO, BANNER], - isBidRequestValid(bid) { - return !!(bid.params.memberid); - }, - interpretResponse(response, bidRequest) { - response = response.body || {}; - if (response.seatbid) { - if (isArray(response.seatbid)) { - const { seatbid } = response; - let winners = seatbid.reduce((bid, ads) => { - let ad = ads.bid.reduce(function (oBid, nBid) { - if (oBid.price < nBid.price) { - const bid = matchRequest(nBid.impid, bidRequest); - const { width, height } = defaultSize(bid); - nBid.cpm = parseFloat(nBid.price).toFixed(2); - nBid.bidId = nBid.impid; - nBid.requestId = nBid.impid; - nBid.width = nBid.w || width; - nBid.height = nBid.h || height; - nBid.ttl = 300; - nBid.mediaType = bid.mediaTypes && bid.mediaTypes.video ? 'video' : 'banner'; - if (nBid.mediaType === 'video') { - nBid.vastXml = cleanVast(nBid.adm, nBid.nurl); - nBid.ttl = 3600; - } - if (nBid.dealid) { - nBid.dealId = nBid.dealid; - } - nBid.uuid = nBid.bidId; - nBid.ad = nBid.adm; - nBid.netRevenue = true; - nBid.creativeId = nBid.crid; - nBid.currency = 'USD'; - nBid.meta = nBid.meta || {}; - if (nBid.adomain && nBid.adomain.length > 0) { - nBid.meta.advertiserDomains = nBid.adomain; - } - return nBid; - } else { - oBid.cpm = oBid.price; - return oBid; - } - }, { price: 0 }); - if (ad.adm) { - bid.push(ad) - } - return bid; - }, []) - let winnersClean = winners.filter(w => { - if (w.bidId) { - return true; - } - return false; - }); - return winnersClean; - } else { - return []; - } - } else { - return []; - } - }, - buildRequests(bidRequest, bidderRequest) { - let timeout = config.getConfig('bidderTimeout'); - let schain = null; - let dmxRequest = { - id: generateUUID(), - cur: ['USD'], - tmax: (timeout - 300), - test: this.test() || 0, - site: { - publisher: { id: String(bidRequest[0].params.memberid) || null } - } - } - - try { - let params = config.getConfig('dmx'); - dmxRequest.user = params.user || {}; - let site = params.site || {}; - dmxRequest.site = { ...dmxRequest.site, ...site } - } catch (e) { - - } - - let eids = []; - if (bidRequest[0] && bidRequest[0].userId) { - bindUserId(eids, deepAccess(bidRequest[0], `userId.idl_env`), 'liveramp.com', 1); - bindUserId(eids, deepAccess(bidRequest[0], `userId.id5id.uid`), 'id5-sync.com', 1); - bindUserId(eids, deepAccess(bidRequest[0], `userId.pubcid`), 'pubcid.org', 1); - bindUserId(eids, deepAccess(bidRequest[0], `userId.tdid`), 'adserver.org', 1); - bindUserId(eids, deepAccess(bidRequest[0], `userId.criteoId`), 'criteo.com', 1); - bindUserId(eids, deepAccess(bidRequest[0], `userId.britepoolid`), 'britepool.com', 1); - bindUserId(eids, deepAccess(bidRequest[0], `userId.lipb.lipbid`), 'liveintent.com', 1); - bindUserId(eids, deepAccess(bidRequest[0], `userId.intentiqid`), 'intentiq.com', 1); - bindUserId(eids, deepAccess(bidRequest[0], `userId.lotamePanoramaId`), 'lotame.com', 1); - bindUserId(eids, deepAccess(bidRequest[0], `userId.parrableId`), 'parrable.com', 1); - bindUserId(eids, deepAccess(bidRequest[0], `userId.netId`), 'netid.de', 1); - dmxRequest.user = dmxRequest.user || {}; - dmxRequest.user.ext = dmxRequest.user.ext || {}; - dmxRequest.user.ext.eids = eids; - } - if (!dmxRequest.test) { - delete dmxRequest.test; - } - if (bidderRequest.gdprConsent) { - dmxRequest.regs = {}; - dmxRequest.regs.ext = {}; - dmxRequest.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0; - - if (bidderRequest.gdprConsent.gdprApplies === true) { - dmxRequest.user = {}; - dmxRequest.user.ext = {}; - dmxRequest.user.ext.consent = bidderRequest.gdprConsent.consentString; - } - } - dmxRequest.regs = dmxRequest.regs || {}; - dmxRequest.regs.coppa = config.getConfig('coppa') === true ? 1 : 0; - if (bidderRequest && bidderRequest.uspConsent) { - dmxRequest.regs = dmxRequest.regs || {}; - dmxRequest.regs.ext = dmxRequest.regs.ext || {}; - dmxRequest.regs.ext.us_privacy = bidderRequest.uspConsent; - } - try { - schain = bidRequest[0].schain; - dmxRequest.source = {}; - dmxRequest.source.ext = {}; - dmxRequest.source.ext.schain = schain || {} - } catch (e) { } - let tosendtags = bidRequest.map(dmx => { - var obj = {}; - obj.id = dmx.bidId; - obj.tagid = String(dmx.params.dmxid || dmx.adUnitCode); - obj.secure = 1; - obj.bidfloor = getFloor(dmx); - if (dmx.mediaTypes && dmx.mediaTypes.video) { - obj.video = { - topframe: 1, - skip: dmx.mediaTypes.video.skip || 0, - linearity: dmx.mediaTypes.video.linearity || 1, - minduration: dmx.mediaTypes.video.minduration || 5, - maxduration: dmx.mediaTypes.video.maxduration || 60, - playbackmethod: dmx.mediaTypes.video.playbackmethod || [2], - api: getApi(dmx.mediaTypes.video), - mimes: dmx.mediaTypes.video.mimes || ['video/mp4'], - protocols: getProtocols(dmx.mediaTypes.video), - h: dmx.mediaTypes.video.playerSize[0][1], - w: dmx.mediaTypes.video.playerSize[0][0] - }; - } else { - obj.banner = { - topframe: 1, - w: cleanSizes(dmx.sizes, 'w'), - h: cleanSizes(dmx.sizes, 'h'), - format: cleanSizes(dmx.sizes).map(s => { - return { w: s[0], h: s[1] }; - }).filter(obj => typeof obj.w === 'number' && typeof obj.h === 'number') - }; - } - return obj; - }); - - if (tosendtags.length <= 5) { - dmxRequest.imp = tosendtags; - return { - method: 'POST', - url: DMXURI, - data: JSON.stringify(dmxRequest), - bidderRequest - } - } else { - return upto5(tosendtags, dmxRequest, bidderRequest, DMXURI); - } - }, - test() { - return window.location.href.indexOf('dmTest=true') !== -1 ? 1 : 0; - }, - getUserSyncs(optionsType, serverResponses, gdprConsent, uspConsent) { - let query = []; - let url = 'https://cdn.districtm.io/ids/index.html' - if (gdprConsent && gdprConsent.gdprApplies && typeof gdprConsent.consentString === 'string') { - query.push(['gdpr', gdprConsent.consentString]) - } - if (uspConsent) { - query.push(['ccpa', uspConsent]) - } - if (query.length > 0) { - url += '?' + query.map(q => q.join('=')).join('&') - } - if (optionsType.iframeEnabled) { - return [{ - type: 'iframe', - url: url - }]; - } - } -} - -export function getFloor(bid) { - let floor = null; - if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: bid.mediaTypes.video ? 'video' : 'banner', - size: bid.sizes.map(size => { - return { - w: size[0], - h: size[1] - } - }) - }); - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { - floor = parseFloat(floorInfo.floor); - } - } - return floor !== null ? floor : bid.params.floor; -} - -export function cleanSizes(sizes, value) { - const supportedSize = [ - { - size: [300, 250], - s: 100 - }, - { - size: [728, 90], - s: 95 - }, - { - size: [320, 50], - s: 90 - }, - { - size: [160, 600], - s: 88 - }, - { - size: [300, 600], - s: 85 - }, - { - size: [300, 50], - s: 80 - }, - { - size: [970, 250], - s: 75 - }, - { - size: [970, 90], - s: 60 - }, - ]; - let newArray = shuffle(sizes, supportedSize); - switch (value) { - case 'w': - return newArray[0][0] || 0; - case 'h': - return newArray[0][1] || 0; - case 'size': - return newArray; - default: - return newArray; - } -} - -export function shuffle(sizes, list) { - let removeSizes = sizes.filter(size => { - return list.map(l => `${l.size[0]}x${l.size[1]}`).indexOf(`${size[0]}x${size[1]}`) === -1 - }) - let reOrder = sizes.reduce((results, current) => { - if (results.length === 0) { - results.push(current); - return results; - } - results.push(current); - results = list.filter(l => results.map(r => `${r[0]}x${r[1]}`).indexOf(`${l.size[0]}x${l.size[1]}`) !== -1); - results = results.sort(function (a, b) { - return b.s - a.s; - }) - return results.map(r => r.size); - }, []) - return removeDuplicate([...reOrder, ...removeSizes]); -} - -export function removeDuplicate(arrayValue) { - return arrayValue.filter((elem, index) => { - return arrayValue.map(e => `${e[0]}x${e[1]}`).indexOf(`${elem[0]}x${elem[1]}`) === index - }) -} - -export function upto5(allimps, dmxRequest, bidderRequest, DMXURI) { - let start = 0; - let step = 5; - let req = []; - while (allimps.length !== 0) { - if (allimps.length >= 5) { - req.push(allimps.splice(start, step)) - } else { - req.push(allimps.splice(start, allimps.length)) - } - } - return req.map(r => { - dmxRequest.imp = r; - return { - method: 'POST', - url: DMXURI, - data: JSON.stringify(dmxRequest), - bidderRequest - } - }) -} - -/** - * Function matchRequest(id: string, BidRequest: object) - * @param id - * @type string - * @param bidRequest - * @type Object - * @returns Object - * - */ -export function matchRequest(id, bidRequest) { - const { bids } = bidRequest.bidderRequest; - const [returnValue] = bids.filter(bid => bid.bidId === id); - return returnValue; -} -export function checkDeepArray(Arr) { - if (Array.isArray(Arr)) { - if (Array.isArray(Arr[0])) { - return Arr[0]; - } else { - return Arr; - } - } else { - return Arr; - } -} -export function defaultSize(thebidObj) { - const { sizes } = thebidObj; - const returnObject = {}; - returnObject.width = checkDeepArray(sizes)[0]; - returnObject.height = checkDeepArray(sizes)[1]; - return returnObject; -} - -export function bindUserId(eids, value, source, atype) { - if (isStr(value) && Array.isArray(eids)) { - eids.push({ - source, - uids: [ - { - id: value, - atype - } - ] - }) - } -} - -export function getApi({ api }) { - let defaultValue = [2]; - if (api && Array.isArray(api) && api.length > 0) { - return api - } else { - return defaultValue; - } -} -export function getPlaybackmethod(playback) { - if (Array.isArray(playback) && playback.length > 0) { - return playback.map(label => { - return VIDEO_MAPPING.playback_method[label] - }) - } - return [2] -} - -export function getProtocols({ protocols }) { - let defaultValue = [2, 3, 5, 6, 7, 8]; - if (protocols && Array.isArray(protocols) && protocols.length > 0) { - return protocols; - } else { - return defaultValue; - } -} - -export function cleanVast(str, nurl) { - try { - const toberemove = /]*?src\s*=\s*['\"]([^'\"]*?)['\"][^>]*?>/ - const [img, url] = str.match(toberemove) - str = str.replace(toberemove, '') - if (img) { - if (url) { - const insrt = `` - str = str.replace('', `${insrt}`) - } - } - return str; - } catch (e) { - if (!nurl) { - return str - } - const insrt = `` - str = str.replace('', `${insrt}`) - return str - } -} -registerBidder(spec); diff --git a/modules/districtmDmxBidAdapter.md b/modules/districtmDmxBidAdapter.md deleted file mode 100644 index 5d5dd2affe6..00000000000 --- a/modules/districtmDmxBidAdapter.md +++ /dev/null @@ -1,203 +0,0 @@ -``` -Module Name: district m Bid Adapter -Module Type: Bidder Adapter -Maintainer: Steve Alliance (steve@districtm.net) -``` - -# Overview - -The `districtmDmxAdapter` module allows publishers to include DMX Exchange demand using Prebid 1.0+. - -## Attributes - -* Single Request -* Multi-Size Support -* GDPR Compliant -* CCPA Compliant -* COPPA Compliant -* Bids returned in **NET** - - ## Media Types - -* Banner -* Video -## Bidder Parameters - -| Key | Scope | Type | Description -| --- | --- | --- | --- -| `dmxid` | Mandatory | Integer | Unique identifier of the placement, dmxid can be obtained in the district m Boost platform. -| `memberid` | Mandatory | Integer | Unique identifier for your account, memberid can be obtained in the district m Boost platform. -| `floor` | Optional | float | Most placement can have floor set in our platform, but this can now be set on the request too. - -# Ad Unit Configuration Example - -```javascript - var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [{ - bidder: 'districtmDMX', - params: { - dmxid: 100001, - memberid: 100003 - } - }] - }]; -``` - -# Ad Unit Configuration Example for video request - -```javascript - var videoAdUnit = { - code: 'video1', - sizes: [640,480], - mediaTypes: { video: {context: 'instream', //or 'outstream' - playerSize: [[640, 480]], - skipppable: true, - minduration: 5, - maxduration: 45, - playback_method: ['auto_play_sound_off', 'viewport_sound_off'], - mimes: ["application/javascript", - "video/mp4"], - - } }, - bids: [ - { - bidder: 'districtmDMX', - params: { - dmxid: '100001', - memberid: '100003', - } - } - - ] - }; -``` - - -# Ad Unit Configuration when COPPA is needed - - -# Quick Start Guide - -###### 1. Including the `districtmDmxAdapter` in your build process. - -Add the adapter as an argument to gulp build. - -``` -gulp build --modules=districtmDmxAdapter,ixBidAdapter,appnexusBidAdapter -``` - -*Adding `"districtmDmxAdapter"` as an entry in a JSON file with your bidders is also acceptable.* - -``` -[ - "districtmDmxAdapter", - "ixBidAdapter", - "appnexusBidAdapter" -] -``` - -*Proceed to build with the JSON file.* - -``` -gulp build --modules=bidderModules.json -``` - -###### 2. Configure the ad unit object - -Once Prebid is ready you may use the below example to create the adUnits object and begin building the configuration. - -```javascript -var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600], [728, 90]], - } - }, - bids: [] - } -]; -``` - -###### 3. Add the bidder - -Our demand and adapter supports multiple sizes per placement, as such a single dmxid may be used for all sizes of a single domain. - -```javascript - var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600], [728, 90]], - } - }, - bids: [{ - bidder: 'districtmDMX', - params: { - dmxid: 100001, - memberid: 100003 - } - }] - }]; -``` - -Our bidder only supports instream context at the moment and we strongly like to put the media types and setting in the ad unit settings. -If no value is set the default value will be applied. - -```javascript - var videoAdUnit = { - code: 'video1', - sizes: [640,480], - mediaTypes: { video: {context: 'instream', //or 'outstream' - playerSize: [[640, 480]], - skipppable: true, - minduration: 5, - maxduration: 45, - playback_method: ['auto_play_sound_off', 'viewport_sound_off'], - mimes: ["application/javascript", - "video/mp4"], - - } }, - bids: [ - { - bidder: 'districtmDMX', - params: { - dmxid: '250258', - memberid: '100600', - } - } - ] - }; -``` - -###### 4. Implementation Checking - -Once the bidder is live in your Prebid configuration you may confirm it is making requests to our end point by looking for requests to `https://dmx.districtm.io/b/v1`. - - -###### 5. Setting first party data - -```code -pbjs.setConfig({ - dmx: { - user: { - 'gender': 'M', - 'yob': 1992, - // keywords example - 'keywords': 'automotive,dodge,engine,car' - - }, - site: { - cat: ['IAB-12'], - pagecat: ['IAB-14'], - sectioncat: ['IAB-24'] - } - } -}); -``` diff --git a/modules/distroscaleBidAdapter.js b/modules/distroscaleBidAdapter.js index 822bea3603a..005dd3e67d6 100644 --- a/modules/distroscaleBidAdapter.js +++ b/modules/distroscaleBidAdapter.js @@ -129,7 +129,8 @@ export const spec = { }, buildRequests: (validBidRequests, bidderRequest) => { - var pageUrl = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || window.location.href; + // TODO: does the fallback to window.location make sense? + var pageUrl = bidderRequest?.refererInfo?.page || window.location.href; var payload = { id: '' + (new Date()).getTime(), @@ -197,7 +198,7 @@ export const spec = { } // First Party Data - const commonFpd = config.getConfig('ortb2') || {}; + const commonFpd = bidderRequest.ortb2 || {}; if (commonFpd.site) { mergeDeep(payload, {site: commonFpd.site}); } diff --git a/modules/divreachBidAdapter.md b/modules/divreachBidAdapter.md deleted file mode 100644 index 643845782b8..00000000000 --- a/modules/divreachBidAdapter.md +++ /dev/null @@ -1,30 +0,0 @@ -# Overview - -Module Name: DivReach Bidder Adapter -Module Type: Bidder Adapter -Maintainer: Zeke@divreach.com - -# Description - -Connects to DivReach demand source to fetch bids. -Please use ```divreach``` as the bidder code. - -# Test Parameters -``` - var adUnits = [ - { - code: 'desktop-banner-ad-div', - sizes: [[300, 250]], - bids: [ - { - bidder: "divreach", - params: { - accountID: '167283', - zoneID: '335105', - domain: 'ad.divreach.com', - } - } - ] - }, - ]; -``` diff --git a/modules/djaxBidAdapter.md b/modules/djaxBidAdapter.md deleted file mode 100644 index d597eb59b58..00000000000 --- a/modules/djaxBidAdapter.md +++ /dev/null @@ -1,50 +0,0 @@ -# Overview - -``` -Module Name: djax Bid Adapter -Module Type: Bidder Adapter -Maintainer : support@djaxtech.com -``` - -# Description - -Connects to Djax Ad Server for bids. - -djax bid adapter supports Banner and Video. - -# Test Parameters -``` - var adUnits = [ - //bannner object - { - code: 'banner-ad-slot', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [{ - bidder: 'djax', - params: { - publisherId: 2 - } - }] - - }, - //video object - { - code: 'video-ad-slot', - mediaTypes: { - video: { - context: 'instream', - playerSize: [640, 480], - }, - }, - bids: [{ - bidder: "djax", - params: { - publisherId: 2 - } - }] - }]; -``` \ No newline at end of file diff --git a/modules/docereeBidAdapter.js b/modules/docereeBidAdapter.js index 737a9f707db..524f464cee3 100644 --- a/modules/docereeBidAdapter.js +++ b/modules/docereeBidAdapter.js @@ -24,6 +24,7 @@ export const spec = { buildRequests: (validBidRequests) => { const serverRequests = []; const { data } = config.getConfig('doceree.user') + // TODO: this should probably look at refererInfo const { page, domain, token } = config.getConfig('doceree.context') const encodedUserInfo = window.btoa(encodeURIComponent(JSON.stringify(data))) diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index da73fdd0177..8850eb282b5 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -22,7 +22,7 @@ export const spec = { const placementId = params.placement; const rnd = Math.floor(Math.random() * 99999999999); - const referrer = bidderRequest.refererInfo.referer; + const referrer = bidderRequest.refererInfo.page; const bidId = bidRequest.bidId; const isDev = params.devMode || false; const pbcode = bidRequest.adUnitCode || false; // div id @@ -70,7 +70,7 @@ export const spec = { } if (params.bcat !== undefined) { - payload.bcat = params.bcat; + payload.bcat = deepAccess(bidderRequest.ortb2Imp, 'bcat') || params.bcat; } if (params.dvt !== undefined) { payload.dvt = params.dvt; @@ -99,7 +99,7 @@ export const spec = { method: 'GET', url: endpoint, data: objectToQueryString(payload), - } + }; }); }, interpretResponse: function(serverResponse, bidRequest) { diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 63332db8725..5f1b46ff9eb 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -1,6 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'e_volution'; const AD_URL = 'https://service.e-volution.ai/?c=o&m=multi'; @@ -64,10 +65,14 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let winTop = window; let location; + // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin try { - location = new URL(bidderRequest.refererInfo.referer) + location = new URL(bidderRequest.refererInfo.page); winTop = window.top; } catch (e) { location = winTop.location; @@ -88,7 +93,7 @@ export const spec = { request.ccpa = bidderRequest.uspConsent; } if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent + request.gdpr = bidderRequest.gdprConsent; } } const len = validBidRequests.length; @@ -101,7 +106,7 @@ export const spec = { bidId: bid.bidId, bidfloor: getBidFloor(bid), eids: [] - } + }; if (bid.userId) { getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com'); diff --git a/modules/ebdrBidAdapter.js b/modules/ebdrBidAdapter.js index 62a3b171b74..a7b1991df9b 100644 --- a/modules/ebdrBidAdapter.js +++ b/modules/ebdrBidAdapter.js @@ -31,7 +31,7 @@ export const spec = { h: whArr[1] }, bidfloor: bidFloor - }) + }); ebdrReq[bid.bidId] = {mediaTypes: _mediaTypes, w: whArr[0], h: whArr[1] diff --git a/modules/edgequeryxBidAdapter.md b/modules/edgequeryxBidAdapter.md deleted file mode 100644 index 265120dfaba..00000000000 --- a/modules/edgequeryxBidAdapter.md +++ /dev/null @@ -1,39 +0,0 @@ -# Overview - -``` -Module Name: Edge Query X Bidder Adapter -Module Type: Bidder Adapter -Maintainer: contact@edgequery.com -``` - -# Description - -Connect to Edge Query X for bids. - -The Edge Query X adapter requires setup and approval from the Edge Query team. -Please reach out to your Technical account manager for more information. - -# Test Parameters - -## Web -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[1, 1]] - } - }, - bids: [ - { - bidder: "edgequeryx", - params: { - accountId: "test", - widgetId: "test" - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/emoteevBidAdapter.md b/modules/emoteevBidAdapter.md deleted file mode 100644 index 226a8374369..00000000000 --- a/modules/emoteevBidAdapter.md +++ /dev/null @@ -1,36 +0,0 @@ -# Overview - -``` -Module Name: Emoteev Bidder Adapter -Module Type: Bidder Adapter -Maintainer: engineering@emoteev.io -``` - -# Description - -Module that connects to Emoteev's demand sources - -# Test Parameters - -``` javascript - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[720, 90]], - } - }, - bids: [ - { - bidder: 'emoteev', - params: { - adSpaceId: 5084, - context: 'footer', - externalId: 42, - } - } - ] - } - ]; -``` diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index 66fd2eb2ac1..eb69e76a837 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -7,13 +7,13 @@ import { isPlainObject, isStr, logError, - logWarn, - parseUrl + logWarn } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {find, includes} from '../src/polyfill.js'; +import {parseDomain} from '../src/refererDetection.js'; const BIDDER_CODE = 'emx_digital'; const ENDPOINT = 'hb.emxdgt.com'; @@ -140,19 +140,12 @@ export const emxAdapter = { logError('emx_digitalBidAdapter', 'error', err); } }, - getReferrer: () => { - try { - return window.top.document.referrer; - } catch (err) { - return document.referrer; - } - }, getSite: (refInfo) => { - let url = parseUrl(refInfo.referer); + // TODO: do the fallbacks make sense? return { - domain: url.hostname, - page: refInfo.referer, - ref: emxAdapter.getReferrer() + domain: refInfo.domain || parseDomain(refInfo.topmostLocation), + page: refInfo.page || refInfo.topmostLocation, + ref: refInfo.ref || window.document.referrer } }, getGdpr: (bidRequests, emxData) => { @@ -294,7 +287,7 @@ export const spec = { emxData = emxAdapter.getGdpr(bidderRequest, Object.assign({}, emxData)); emxData = emxAdapter.getSupplyChain(bidderRequest, Object.assign({}, emxData)); if (bidderRequest && bidderRequest.uspConsent) { - emxData.us_privacy = bidderRequest.uspConsent + emxData.us_privacy = bidderRequest.uspConsent; } // adding eid support diff --git a/modules/engageyaBidAdapter.js b/modules/engageyaBidAdapter.js index 95ab8ecbd03..a66e825e5df 100644 --- a/modules/engageyaBidAdapter.js +++ b/modules/engageyaBidAdapter.js @@ -1,6 +1,8 @@ import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { createTrackPixelHtml } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + const BIDDER_CODE = 'engageya'; const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json'; const ENDPOINT_METHOD = 'GET'; @@ -13,9 +15,10 @@ function getPageUrl(bidRequest, bidderRequest) { if (bidRequest.params.pageUrl && bidRequest.params.pageUrl != '[PAGE_URL]') { return bidRequest.params.pageUrl; } - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - return bidderRequest.refererInfo.referer; + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + return bidderRequest.refererInfo.page; } + // TODO: does this fallback make sense? const pageUrl = (isInIframe() && document.referrer) ? document.referrer : window.location.href; @@ -125,6 +128,9 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + if (!validBidRequests) { return []; } diff --git a/modules/enrichmentFpdModule.js b/modules/enrichmentFpdModule.js index 9268c81c033..ed9381f4e4d 100644 --- a/modules/enrichmentFpdModule.js +++ b/modules/enrichmentFpdModule.js @@ -5,7 +5,7 @@ */ import { timestamp, mergeDeep } from '../src/utils.js'; import { submodule } from '../src/hook.js'; -import { getRefererInfo } from '../src/refererDetection.js'; +import {getRefererInfo, parseDomain} from '../src/refererDetection.js'; import { getCoreStorageManager } from '../src/storageManager.js'; let ortb2 = {}; @@ -70,30 +70,21 @@ export function findRootDomain(fullDomain = window.location.hostname) { * Checks for referer and if exists merges into ortb2 global data */ function setReferer() { - if (getRefererInfo().referer) mergeDeep(ortb2, { site: { ref: getRefererInfo().referer } }); + if (getRefererInfo().ref) mergeDeep(ortb2, { site: { ref: getRefererInfo().ref } }); } /** * Checks for canonical url and if exists merges into ortb2 global data */ function setPage() { - if (getRefererInfo().canonicalUrl) mergeDeep(ortb2, { site: { page: getRefererInfo().canonicalUrl } }); + if (getRefererInfo().page) mergeDeep(ortb2, { site: { page: getRefererInfo().page } }); } /** * Checks for canonical url and if exists retrieves domain and merges into ortb2 global data */ function setDomain() { - let parseDomain = function(url) { - if (!url || typeof url !== 'string' || url.length === 0) return; - - var match = url.match(/^(?:https?:\/\/)?(?:www\.)?(.*?(?=(\?|\#|\/|$)))/i); - - return match && match[1]; - }; - - let domain = parseDomain(getRefererInfo().canonicalUrl) - + const domain = parseDomain(getRefererInfo().page, {noLeadingWww: true}); if (domain) { mergeDeep(ortb2, { site: { domain: domain } }); mergeDeep(ortb2, { site: { publisher: { domain: findRootDomain(domain) } } }); @@ -133,6 +124,16 @@ function setKeywords() { if (keywords && keywords.content) mergeDeep(ortb2, { site: { keywords: keywords.content.replace(/\s/g, '') } }); } +/** + * Checks the Global Privacy Control status, and if exists and is true, merges into regs.ext.gpc + */ +function setGpc() { + const gpcValue = navigator.globalPrivacyControl; + if (gpcValue) { + mergeDeep(ortb2, { regs: { ext: { gpc: 1 } } }) + } +} + /** * Resets modules global ortb2 data */ @@ -144,6 +145,7 @@ function runEnrichments() { setDomain(); setDimensions(); setKeywords(); + setGpc(); return ortb2; } @@ -151,17 +153,19 @@ function runEnrichments() { /** * Sets default values to ortb2 if exists and adds currency and ortb2 setConfig callbacks on init */ -export function initSubmodule(fpdConf, data) { +export function processFpd(fpdConf, {global}) { resetOrtb2(); - return (!fpdConf.skipEnrichments) ? mergeDeep(runEnrichments(), data) : data; + return { + global: (!fpdConf.skipEnrichments) ? mergeDeep(runEnrichments(), global) : global + }; } /** @type {firstPartyDataSubmodule} */ export const enrichmentsSubmodule = { name: 'enrichments', queue: 2, - init: initSubmodule + processFpd } submodule('firstPartyData', enrichmentsSubmodule) diff --git a/modules/envivoBidAdapter.md b/modules/envivoBidAdapter.md deleted file mode 100644 index 3ecc8a251f3..00000000000 --- a/modules/envivoBidAdapter.md +++ /dev/null @@ -1,50 +0,0 @@ -# Overview - -``` -Module Name: envivo Bid Adapter -Module Type: Bidder Adapter -Maintainer : adtech@nvivo.tv -``` - -# Description - -Connects to Envivo Ad Server for bids. - -envivo bid adapter supports Banner and Video. - -# Test Parameters -``` - var adUnits = [ - //bannner object - { - code: 'banner-ad-slot', - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - bids: [{ - bidder: 'envivo', - params: { - publisherId: 14 - } - }] - - }, - //video object - { - code: 'video-ad-slot', - mediaTypes: { - video: { - context: 'instream', - playerSize: [640, 480], - }, - }, - bids: [{ - bidder: "envivo", - params: { - publisherId: 14 - } - }] - }]; -``` diff --git a/modules/eplanningAnalyticsAdapter.js b/modules/eplanningAnalyticsAdapter.js index 365f91382f7..9eb701b8ecc 100644 --- a/modules/eplanningAnalyticsAdapter.js +++ b/modules/eplanningAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { logError } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 780531964ad..0d37b72f4ad 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -1,7 +1,7 @@ -import { isEmpty, getWindowSelf, parseSizesInput } from '../src/utils.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js'; +import {getWindowSelf, isEmpty, parseSizesInput, isGptPubadsDefined, isSlotMatchingAdUnitCode} from '../src/utils.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'eplanning'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -36,18 +36,16 @@ export const spec = { const urlConfig = getUrlConfig(bidRequests); const pcrs = getCharset(); const spaces = getSpaces(bidRequests, urlConfig.ml); - const pageUrl = bidderRequest.refererInfo.referer; - const getDomain = (url) => { - let anchor = document.createElement('a'); - anchor.href = url; - return anchor.hostname; - } + // TODO: do the fallbacks make sense here? + const pageUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const domain = bidderRequest.refererInfo.domain || window.location.host; if (urlConfig.t) { url = 'https://' + urlConfig.isv + '/layers/t_pbjs_2.json'; params = {}; } else { - url = 'https://' + (urlConfig.sv || DEFAULT_SV) + '/pbjs/1/' + urlConfig.ci + '/' + dfpClientId + '/' + getDomain(pageUrl) + '/' + sec; - const referrerUrl = bidderRequest.refererInfo.referer.reachedTop ? window.top.document.referrer : bidderRequest.refererInfo.referer; + url = 'https://' + (urlConfig.sv || DEFAULT_SV) + '/pbjs/1/' + urlConfig.ci + '/' + dfpClientId + '/' + domain + '/' + sec; + // TODO: does the fallback make sense here? + const referrerUrl = bidderRequest.refererInfo.ref || bidderRequest.refererInfo.topmostLocation; if (storage.hasLocalStorage()) { registerViewabilityAllBids(bidRequests); @@ -152,7 +150,7 @@ export const spec = { return syncs; }, -} +}; function getUserAgent() { return window.navigator.userAgent; @@ -293,13 +291,25 @@ function getCharset() { function waitForElementsPresent(elements) { const observer = new MutationObserver(function (mutationList, observer) { + let index; + let adView; if (mutationList && Array.isArray(mutationList)) { mutationList.forEach(mr => { if (mr && mr.addedNodes && Array.isArray(mr.addedNodes)) { mr.addedNodes.forEach(ad => { - let index = elements.indexOf(ad.id); + index = elements.indexOf(ad.id); + adView = ad; + if (index < 0) { + elements.forEach(code => { + let div = _getAdSlotHTMLElement(code); + if (div && div.contains(ad) && div.getBoundingClientRect().width > 0) { + index = elements.indexOf(div.id); + adView = div; + } + }); + } if (index >= 0) { - registerViewability(ad); + registerViewability(adView, elements[index]); elements.splice(index, 1); if (!elements.length) { observer.disconnect(); @@ -320,19 +330,41 @@ function waitForElementsPresent(elements) { }); } -function registerViewability(div) { +function registerViewability(div, name) { visibilityHandler({ - name: div.id, + name: name, div: div }); } +function _mapAdUnitPathToElementId(adUnitCode) { + if (isGptPubadsDefined()) { + // eslint-disable-next-line no-undef + const adSlots = googletag.pubads().getSlots(); + const isMatchingAdSlot = isSlotMatchingAdUnitCode(adUnitCode); + + for (let i = 0; i < adSlots.length; i++) { + if (isMatchingAdSlot(adSlots[i])) { + const id = adSlots[i].getSlotElementId(); + return id; + } + } + } + + return null; +} + +function _getAdSlotHTMLElement(adUnitCode) { + return document.getElementById(adUnitCode) || + document.getElementById(_mapAdUnitPathToElementId(adUnitCode)); +} + function registerViewabilityAllBids(bids) { let elementsNotPresent = []; bids.forEach(bid => { - let div = document.getElementById(bid.adUnitCode); + let div = _getAdSlotHTMLElement(bid.adUnitCode); if (div) { - registerViewability(div); + registerViewability(div, bid.adUnitCode); } else { elementsNotPresent.push(bid.adUnitCode); } @@ -347,114 +379,65 @@ function getViewabilityTracker() { let VIEWABILITY_TIME = 1000; let VIEWABILITY_MIN_RATIO = 0.5; let publicApi; - let context; - - function segmentIsOutsideTheVisibleRange(visibleRangeEnd, p1, p2) { - return p1 > visibleRangeEnd || p2 < 0; - } - - function segmentBeginsBeforeTheVisibleRange(p1) { - return p1 < 0; - } - - function segmentEndsAfterTheVisibleRange(visibleRangeEnd, p2) { - return p2 < visibleRangeEnd; - } - - function axialVisibilityRatio(visibleRangeEnd, p1, p2) { - let visibilityRatio = 0; - if (!segmentIsOutsideTheVisibleRange(visibleRangeEnd, p1, p2)) { - if (segmentBeginsBeforeTheVisibleRange(p1)) { - visibilityRatio = p2 / (p2 - p1); + let observer; + let visibilityAds = {}; + + function intersectionCallback(entries) { + entries.forEach(function(entry) { + var adBox = entry.target; + if (entry.isIntersecting) { + if (entry.intersectionRatio >= VIEWABILITY_MIN_RATIO && entry.boundingClientRect && entry.boundingClientRect.height > 0 && entry.boundingClientRect.width > 0) { + visibilityAds[adBox.id] = true; + } } else { - visibilityRatio = segmentEndsAfterTheVisibleRange(visibleRangeEnd, p2) ? 1 : (visibleRangeEnd - p1) / (p2 - p1); + visibilityAds[adBox.id] = false; } - } - return visibilityRatio; - } - - function isNotHiddenByNonFriendlyIframe() { - try { return (window === window.top) || window.frameElement; } catch (e) {} - } - - function defineContext(e) { - try { - context = e && window.document.body.contains(e) ? window : (window.top.document.body.contains(e) ? top : undefined); - } catch (err) {} - return context; - } - - function getContext(e) { - return context; - } - - function verticalVisibilityRatio(position) { - return axialVisibilityRatio(getContext().innerHeight, position.top, position.bottom); - } - - function horizontalVisibilityRatio(position) { - return axialVisibilityRatio(getContext().innerWidth, position.left, position.right); - } - - function itIsNotHiddenByBannerAreaPosition(e) { - let position = e.getBoundingClientRect(); - return (verticalVisibilityRatio(position) * horizontalVisibilityRatio(position)) > VIEWABILITY_MIN_RATIO; - } - - function itIsNotHiddenByDisplayStyleCascade(e) { - return e.offsetHeight > 0 && e.offsetWidth > 0; - } - - function itIsNotHiddenByOpacityStyleCascade(e) { - let s = e.style; - let p = e.parentNode; - return !(s && parseFloat(s.opacity) === 0) && (!p || itIsNotHiddenByOpacityStyleCascade(p)); - } - - function itIsNotHiddenByVisibilityStyleCascade(e) { - return getContext().getComputedStyle(e).visibility !== 'hidden'; - } - - function itIsNotHiddenByTabFocus() { - try { return getContext().top.document.hasFocus(); } catch (e) {} - } - - function isDefined(e) { - return (e !== null) && (typeof e !== 'undefined'); + }); } - function itIsNotHiddenByOrphanBranch() { - return isDefined(getContext()); + function observedElementIsVisible(element) { + return visibilityAds[element.id] && document.visibilityState && document.visibilityState === 'visible'; } - function isContextInAnIframe() { - return isDefined(getContext().frameElement); + function defineObserver() { + if (!observer) { + var observerConfig = { + root: null, + rootMargin: '0px', + threshold: [VIEWABILITY_MIN_RATIO] + }; + observer = new IntersectionObserver(intersectionCallback.bind(this), observerConfig); + } } - function processIntervalVisibilityStatus(elapsedVisibleIntervals, element, callback) { - let visibleIntervals = isVisible(element) ? (elapsedVisibleIntervals + 1) : 0; + let visibleIntervals = observedElementIsVisible(element) ? (elapsedVisibleIntervals + 1) : 0; if (visibleIntervals === TIME_PARTITIONS) { + stopObserveViewability(element) callback(); } else { setTimeout(processIntervalVisibilityStatus.bind(this, visibleIntervals, element, callback), VIEWABILITY_TIME / TIME_PARTITIONS); } } - function isVisible(element) { - defineContext(element); - return isNotHiddenByNonFriendlyIframe() && - itIsNotHiddenByOrphanBranch() && - itIsNotHiddenByTabFocus() && - itIsNotHiddenByDisplayStyleCascade(element) && - itIsNotHiddenByVisibilityStyleCascade(element) && - itIsNotHiddenByOpacityStyleCascade(element) && - itIsNotHiddenByBannerAreaPosition(element) && - (!isContextInAnIframe() || isVisible(getContext().frameElement)); + function stopObserveViewability(element) { + delete visibilityAds[element.id]; + observer.unobserve(element); + } + + function observeAds(element) { + observer.observe(element); + } + + function initAndVerifyVisibility(element, callback) { + if (element) { + defineObserver(); + observeAds(element); + processIntervalVisibilityStatus(0, element, callback); + } } publicApi = { - isVisible: isVisible, - onView: processIntervalVisibilityStatus.bind(this, 0) + onView: initAndVerifyVisibility.bind(this) }; return publicApi; diff --git a/modules/etargetBidAdapter.js b/modules/etargetBidAdapter.js index f7d552b1b09..b290a62420c 100644 --- a/modules/etargetBidAdapter.js +++ b/modules/etargetBidAdapter.js @@ -1,5 +1,4 @@ import { deepSetValue, isFn, isPlainObject } from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -91,7 +90,7 @@ export const spec = { mts['title'] = [(document.getElementsByTagName('title')[0] || []).innerHTML]; mts['base'] = [(document.getElementsByTagName('base')[0] || {}).href]; mts['referer'] = [document.location.href]; - mts['ortb2'] = (config.getConfig('ortb2') || {}); + mts['ortb2'] = (bidderRequest.ortb2 || {}); } catch (e) { mts.error = e; } diff --git a/modules/eywamediaBidAdapter.md b/modules/eywamediaBidAdapter.md deleted file mode 100644 index 76b9b032c1b..00000000000 --- a/modules/eywamediaBidAdapter.md +++ /dev/null @@ -1,37 +0,0 @@ -# Overview - -``` -Module Name: Eywamedia Bid Adapter -Module Type: Bidder Adapter -Maintainer: sharath@eywamedia.com -Note: Our ads will only render in mobile and desktop -``` - -# Description - -Connects to Eywamedia Ad Server for bids. - -Eywamedia bid adapter supports Banners. - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'div-gpt-ad-1460505748561-0', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'eywamedia', - params: { - publisherId: 'f63a2362-5aa4-4829-bbd2-2678ced8b63e', //Required - GUID (may include numbers and characters) - bidFloor: 0.50, // optional - cats: ["iab1-1","iab23-2"], // optional - keywords: ["sports", "cricket"], // optional - lat: 12.33333, // optional - lon: 77.32322, // optional - locn: "country$region$city$zip" // optional - } - }] - } -]; -``` diff --git a/modules/fabrickIdSystem.js b/modules/fabrickIdSystem.js index 08eb2d4f043..dfc2985b0db 100644 --- a/modules/fabrickIdSystem.js +++ b/modules/fabrickIdSystem.js @@ -70,10 +70,10 @@ export const fabrickIdSubmodule = { } } // pull off the trailing & - url = url.slice(0, -1) + url = url.slice(0, -1); const referer = _getRefererInfo(configParams); const refs = new Map(); - _setReferrer(refs, referer.referer); + _setReferrer(refs, referer.topmostLocation); if (referer.stack && referer.stack[0]) { _setReferrer(refs, referer.stack[0]); } @@ -174,7 +174,7 @@ export function appendUrl(url, paramName, s, configParams) { s = s.substring(0, thisMaxRefLen - 2); } } - return `${url}${s}` + return `${url}${s}`; } else { return url; } diff --git a/modules/fairtradeBidAdapter.md b/modules/fairtradeBidAdapter.md deleted file mode 100644 index 56abb84d15a..00000000000 --- a/modules/fairtradeBidAdapter.md +++ /dev/null @@ -1,28 +0,0 @@ -# Overview - -Module Name: FairTrade Bidder Adapter -Module Type: Bidder Adapter -Maintainer: Tammy.l@VaticDigital.com - -# Description - -Module that connects to FairTrade demand source to fetch bids. - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: "fairtrade", - params: { - uid: '166', - priceType: 'gross' // by default is 'net' - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 6fb39c49ec8..d695292bb4a 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -206,7 +206,8 @@ function buildRequests(validBidRequests, bidderRequest) { }) }); data.bids.forEach(bid => BID_METADATA[bid.bidId] = { - referer: data.refererInfo.referer, + // TODO: is 'page' the right value here? + referer: data.refererInfo.page, transactionId: bid.transactionId }); if (bidderRequest.gdprConsent) { diff --git a/modules/fidelityBidAdapter.md b/modules/fidelityBidAdapter.md deleted file mode 100644 index 0af75689bd6..00000000000 --- a/modules/fidelityBidAdapter.md +++ /dev/null @@ -1,30 +0,0 @@ -# Overview -​ -**Module Name**: Fidelity Media fmxSSP Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: on@fidelity-media.com -​ -# Description -​ -Connects to Fidelity Media fmxSSP demand source to fetch bids. -​ -# Test Parameters -``` - var adUnits = [{ - code: 'banner-ad-div', - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - bids: [{ - bidder: 'fidelity', - params: { - zoneid: '27248', - floor: 0.005, - server: 'x.fidelity-media.com' - } - }] - }]; - -``` \ No newline at end of file diff --git a/modules/finativeBidAdapter.js b/modules/finativeBidAdapter.js new file mode 100644 index 00000000000..6368fc011cc --- /dev/null +++ b/modules/finativeBidAdapter.js @@ -0,0 +1,238 @@ +// jshint esversion: 6, es3: false, node: true +'use strict'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { NATIVE } from '../src/mediaTypes.js'; +import { _map, deepSetValue, isEmpty, deepAccess } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +const BIDDER_CODE = 'finative'; +const DEFAULT_CUR = 'EUR'; +const ENDPOINT_URL = 'https://b.finative.cloud/cds/rtb/bid?format=openrtb2.5&ssp=pb'; + +const NATIVE_ASSET_IDS = {0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon'}; + +const NATIVE_PARAMS = { + title: { + id: 0, + name: 'title' + }, + + body: { + id: 1, + name: 'data', + type: 2 + }, + + sponsoredBy: { + id: 2, + name: 'data', + type: 1 + }, + + image: { + id: 3, + type: 3, + name: 'img' + }, + + cta: { + id: 4, + type: 12, + name: 'data' + }, + + icon: { + id: 5, + type: 1, + name: 'img' + } +}; + +export const spec = { + code: BIDDER_CODE, + + supportedMediaTypes: [NATIVE], + + isBidRequestValid: function(bid) { + return !!bid.params.adUnitId; + }, + + buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; + const tid = validBidRequests[0].transactionId; + const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; + let url = bidderRequest.refererInfo.referer; + + const imp = validBidRequests.map((bid, id) => { + const assets = _map(bid.nativeParams, (bidParams, key) => { + const props = NATIVE_PARAMS[key]; + + const asset = { + required: bidParams.required & 1 + }; + + if (props) { + asset.id = props.id; + + let w, h; + + if (bidParams.sizes) { + w = bidParams.sizes[0]; + h = bidParams.sizes[1]; + } + + asset[props.name] = { + len: bidParams.len, + type: props.type, + w, + h + }; + + return asset; + } + }) + .filter(Boolean); + + if (bid.params.url) { + url = bid.params.url; + } + + return { + id: String(id + 1), + tagid: bid.params.adUnitId, + tid: tid, + pt: pt, + native: { + request: { + assets + } + } + }; + }); + + const request = { + id: bidderRequest.auctionId, + site: { + page: url + }, + device: { + ua: navigator.userAgent + }, + cur, + imp, + user: {}, + regs: { + ext: { + gdpr: 0, + pb_ver: '$prebid.version$' + } + } + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(request), + options: { + contentType: 'application/json' + }, + bids: validBidRequests + }; + }, + + interpretResponse: function(serverResponse, { bids }) { + if (isEmpty(serverResponse.body)) { + return []; + } + + const { seatbid, cur } = serverResponse.body; + + const bidResponses = (typeof seatbid != 'undefined') ? flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + result[bid.impid - 1] = bid; + return result; + }, []) : []; + + return bids + .map((bid, id) => { + const bidResponse = bidResponses[id]; + + if (bidResponse) { + return { + requestId: bid.bidId, + cpm: bidResponse.price, + creativeId: bidResponse.crid, + ttl: 1000, + netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'), + currency: cur, + mediaType: NATIVE, + bidderCode: BIDDER_CODE, + native: parseNative(bidResponse), + meta: { + advertiserDomains: bidResponse.adomain && bidResponse.adomain.length > 0 ? bidResponse.adomain : [] + } + }; + } + }) + .filter(Boolean); + } +}; + +registerBidder(spec); + +function parseNative(bid) { + const {assets, link, imptrackers} = bid.adm.native; + + let clickUrl = link.url.replace(/\$\{AUCTION_PRICE\}/g, bid.price); + + if (link.clicktrackers) { + link.clicktrackers.forEach(function (clicktracker, index) { + link.clicktrackers[index] = clicktracker.replace(/\$\{AUCTION_PRICE\}/g, bid.price); + }); + } + + if (imptrackers) { + imptrackers.forEach(function (imptracker, index) { + imptrackers[index] = imptracker.replace(/\$\{AUCTION_PRICE\}/g, bid.price); + }); + } + + const result = { + url: clickUrl, + clickUrl: clickUrl, + clickTrackers: link.clicktrackers || undefined, + impressionTrackers: imptrackers || undefined + }; + + assets.forEach(asset => { + const kind = NATIVE_ASSET_IDS[asset.id]; + const content = kind && asset[NATIVE_PARAMS[kind].name]; + + if (content) { + result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + + return result; +} + +function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +function flatten(arr) { + return [].concat(...arr); +} diff --git a/modules/finativeBidAdapter.md b/modules/finativeBidAdapter.md new file mode 100644 index 00000000000..74479150fe4 --- /dev/null +++ b/modules/finativeBidAdapter.md @@ -0,0 +1,45 @@ +# Overview +Module Name: Finative Bidder Adapter +Type: Finative Adapter +Maintainer: tech@finative.cloud + +# Description +Finative Bidder Adapter for Prebid.js. + +# Test Parameters +``` +var adUnits = [{ + code: 'test-div', + + mediaTypes: { + native: { + title: { + required: true, + len: 50 + }, + body: { + required: true, + len: 350 + }, + url: { + required: true + }, + image: { + required: true, + sizes : [300, 175] + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'finative', + params: { + url : "https://mockup.finative.cloud", + adUnitId: "1uyo" + } + }] +}]; +``` + diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js index 7ecc7e963b5..88c5f85f15d 100644 --- a/modules/fintezaAnalyticsAdapter.js +++ b/modules/fintezaAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { parseUrl, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; import CONSTANTS from '../src/constants.json'; @@ -35,16 +35,8 @@ function getPageInfo() { } function getUniqId() { - let cookies; - - try { - cookies = parseCookies(document.cookie); - } catch (a) { - cookies = {}; - } - let isUniqFromLS; - let uniq = cookies[ UNIQ_ID_KEY ]; + let uniq = storage.getCookie(UNIQ_ID_KEY); if (!uniq) { try { if (storage.hasLocalStorage()) { @@ -189,7 +181,7 @@ function initSession() { !checkSessionByExpires() || !checkSessionByReferer() || !checkSessionByDay()) { - sessionId = '' + timestamp + getRandAsStr(SESSION_RAND_PART); + sessionId = '' + timestamp + getRandAsStr(SESSION_RAND_PART); // lgtm [js/insecure-randomness] begin = timestamp; isNew = true; diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js new file mode 100644 index 00000000000..0f7092baf03 --- /dev/null +++ b/modules/fledgeForGpt.js @@ -0,0 +1,52 @@ +/** + * Fledge modules is responsible for registering fledged auction configs into the GPT slot; + * GPT is resposible to run the fledge auction. + */ +import { config } from '../src/config.js'; +import { getHook } from '../src/hook.js'; +import { getGptSlotForAdUnitCode, logInfo, logWarn } from '../src/utils.js'; + +const MODULE = 'fledgeForGpt' + +export let isEnabled = false; + +config.getConfig('fledgeForGpt', config => init(config.fledgeForGpt)); + +/** + * Module init. + */ +export function init(cfg) { + if (cfg && cfg.enabled === true) { + if (!isEnabled) { + getHook('addComponentAuction').before(addComponentAuctionHook); + isEnabled = true; + } + logInfo(MODULE, `isEnabled`, cfg); + } else { + if (isEnabled) { + getHook('addComponentAuction').getHooks({hook: addComponentAuctionHook}).remove(); + isEnabled = false; + } + logInfo(MODULE, `isDisabled`, cfg); + } +} + +export function addComponentAuctionHook(next, bidRequest, componentAuctionConfig) { + const seller = componentAuctionConfig.seller; + const adUnitCode = bidRequest.adUnitCode; + const gptSlot = getGptSlotForAdUnitCode(adUnitCode); + if (gptSlot && gptSlot.setConfig) { + delete componentAuctionConfig.bidId; + gptSlot.setConfig({ + componentAuction: [{ + configKey: seller, + auctionConfig: componentAuctionConfig + }] + }); + logInfo(MODULE, `register component auction config for: ${adUnitCode} x ${seller}: ${gptSlot.getAdUnitPath()}`, componentAuctionConfig); + } else { + logWarn(MODULE, `unable to register component auction config for: ${adUnitCode} x ${seller}.`); + } + + next(bidRequest, componentAuctionConfig); +} diff --git a/modules/fledgeForGpt.md b/modules/fledgeForGpt.md new file mode 100644 index 00000000000..5672ac04618 --- /dev/null +++ b/modules/fledgeForGpt.md @@ -0,0 +1,60 @@ +## Overview + +This module enables the handling of fledge auction by GPT. +Bid adapters can now return component auction configs in addition to bids. + +Those component auction configs will be registered to the GPT ad slot, +and GPT will be responsible to run fledge auction and render the winning fledge ad. + +Find out more [here](https://github.com/WICG/turtledove/blob/main/FLEDGE.md). + +## Integration + +Build the fledge module into the Prebid.js package with: + +``` +gulp build --modules=fledgeForGpt,... +``` + +## Module Configuration Parameters + +|Name |Type |Description |Notes | +| :------------ | :------------ | :------------ |:------------ | +|enabled | Boolean |Enable/disable the module |Defaults to `false` | + +## Configuration + +Fledge support need to be enabled at 3 levels: +- `fledgeForGpt` module +- bidder +- adunit +### enabling the fledgeForGpt module +```js +pbjs.que.push(function() { + pbjs.setConfig({ + fledgeForGpt: { + enabled: true + } + }); +}); +``` + +### enablingthe bidder +```js +pbjs.setBidderConfig({ + bidders: ["openx"], + config: { + fledgeEnabled: true + } +}); +``` + +### enabling the adunit +ortb2Imp.ext.ae on the adunit must be set 1 +```js +ortb2Imp: { + ext: { + ae: 1 + } +} +``` diff --git a/modules/flocIdSystem.js b/modules/flocIdSystem.js index 3fddbaa7129..e69de29bb2d 100644 --- a/modules/flocIdSystem.js +++ b/modules/flocIdSystem.js @@ -1,105 +0,0 @@ -/** - * This module adds flocId to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/flocId - * @requires module:modules/userId - */ - -import { logInfo, logError } from '../src/utils.js'; -import {submodule} from '../src/hook.js' - -const MODULE_NAME = 'flocId'; - -/** - * Add meta tag to support enabling of floc origin trial - * @function - * @param {string} token - configured token for origin-trial - */ -function enableOriginTrial(token) { - const tokenElement = document.createElement('meta'); - tokenElement.httpEquiv = 'origin-trial'; - tokenElement.content = token; - document.head.appendChild(tokenElement); -} - -/** - * Get the interest cohort. - * @param successCallback - * @param errorCallback - */ -function getFlocData(successCallback, errorCallback) { - errorCallback('The Floc has flown'); -} - -/** - * Encode the id - * @param value - * @returns {string|*} - */ -function encodeId(value) { - const result = {}; - if (value) { - result.flocId = value; - logInfo('Decoded value ' + JSON.stringify(result)); - return result; - } - return undefined; -} - -/** @type {Submodule} */ -export const flocIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - - /** - * decode the stored id value for passing to bid requests - * @function - * @param {string} value - * @returns {{flocId:{ id: string }} or undefined if value doesn't exists - */ - decode(value) { - return (value) ? encodeId(value) : undefined; - }, - /** - * If chrome and cohort enabled performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [config] - * @returns {IdResponse|undefined} - */ - getId(config) { - // Block usage of storage of cohort ID - const checkStorage = (config && config.storage); - if (checkStorage) { - logError('User ID - flocId submodule storage should not defined'); - return; - } - // Validate feature is enabled - const isFlocEnabled = false; - - if (isFlocEnabled) { - const configParams = (config && config.params) || {}; - if (configParams && (typeof configParams.token === 'string')) { - // Insert meta-tag with token from configuration - enableOriginTrial(configParams.token); - } - // Example expected output { "id": "14159", "version": "chrome.1.0" } - let returnCallback = (cb) => { - getFlocData((data) => { - returnCallback = () => { return data; } - logInfo('Cohort id: ' + JSON.stringify(data)); - cb(data); - }, (err) => { - logInfo(err); - cb(undefined); - }); - }; - - return {callback: returnCallback}; - } - } -}; - -submodule('userId', flocIdSubmodule); diff --git a/modules/flocIdSystem.md b/modules/flocIdSystem.md deleted file mode 100644 index 07184700a14..00000000000 --- a/modules/flocIdSystem.md +++ /dev/null @@ -1,34 +0,0 @@ -## FloC ID User ID Submodule - -### Building Prebid with Floc Id Support -Your Prebid build must include the modules for both **userId** and **flocIdSystem** submodule. Follow the build instructions for Prebid as -explained in the top level README.md file of the Prebid source tree. - -ex: $ gulp build --modules=userId,flocIdSystem - -### Prebid Params - -Individual params may be set for the FloC ID User ID Submodule. -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'flocId', - params: { - token: "Registered token or default sharedid.org token" - } - }] - } -}); -``` - -### Parameter Descriptions for the `userSync` Configuration Section -The below parameters apply only to the FloC ID User ID Module integration. - -| Params under usersync.userIds[]| Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | ID value for the Floc ID module - `"flocId"` | `"flocId"` | -| params | Optional | Object | Details for flocId syncing. | | -| params.token | Optional | Object | Publisher registered token.To get new token, register https://developer.chrome.com/origintrials/#/trials/active for Federated Learning of Cohorts. Default sharedid.org token: token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9"| token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9" - | -| storage | Not Allowed | Object | Will ask browser for cohort everytime. Setting storage will fail id lookup || diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js index 44b9f3bf217..ea634027dbe 100644 --- a/modules/fluctBidAdapter.js +++ b/modules/fluctBidAdapter.js @@ -39,7 +39,8 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { const serverRequests = []; - const referer = bidderRequest.refererInfo.referer; + // TODO: is 'page' the right value here? + const referer = bidderRequest.refererInfo.page; _each(validBidRequests, (request) => { const data = Object(); diff --git a/modules/fpdModule/index.js b/modules/fpdModule/index.js index 427547a4e4d..553cab47b66 100644 --- a/modules/fpdModule/index.js +++ b/modules/fpdModule/index.js @@ -4,55 +4,49 @@ */ import { config } from '../../src/config.js'; import { module, getHook } from '../../src/hook.js'; -import { getGlobal } from '../../src/prebidGlobal.js'; -import { addBidderRequests } from '../../src/auction.js'; +import {logError} from '../../src/utils.js'; +import {GreedyPromise} from '../../src/utils/promise.js'; let submodules = []; -/** - * enable submodule in User ID - * @param {RtdSubmodule} submodule - */ export function registerSubmodules(submodule) { submodules.push(submodule); } -export function init() { - let modConf = config.getConfig('firstPartyData') || {}; - let ortb2 = config.getConfig('ortb2') || {}; +export function reset() { + submodules.length = 0; +} +export function processFpd({global = {}, bidder = {}} = {}) { + let modConf = config.getConfig('firstPartyData') || {}; + let result = GreedyPromise.resolve({global, bidder}); submodules.sort((a, b) => { return ((a.queue || 1) - (b.queue || 1)); }).forEach(submodule => { - ortb2 = submodule.init(modConf, ortb2); + result = result.then( + ({global, bidder}) => GreedyPromise.resolve(submodule.processFpd(modConf, {global, bidder})) + .catch((err) => { + logError(`Error in FPD module ${submodule.name}`, err); + return {}; + }) + .then((result) => ({global: result.global || global, bidder: result.bidder || bidder})) + ); }); - - config.setConfig({ortb2}); + return result; } -/** - * BidderRequests hook to intiate module and reset modules ortb2 data object - */ -function addBidderRequestHook(fn, bidderRequests) { - init(); - fn.call(this, bidderRequests); - // Removes hook after run - addBidderRequests.getHooks({ hook: addBidderRequestHook }).remove(); +export function startAuctionHook(fn, req) { + processFpd(req.ortb2Fragments).then((ortb2Fragments) => { + Object.assign(req.ortb2Fragments, ortb2Fragments); + fn.call(this, req); + }) } -/** - * Sets bidderRequests hook - */ function setupHook() { - getHook('addBidderRequests').before(addBidderRequestHook); + getHook('startAuction').before(startAuctionHook, 10); } module('firstPartyData', registerSubmodules); // Runs setupHook on initial load setupHook(); - -/** - * Global function to reinitiate module - */ -(getGlobal()).refreshFpd = setupHook; diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index eca31dd5a95..38973f915b6 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -189,7 +189,7 @@ function formatAdHTML(bid, size) { var libUrl = ''; if (integrationType && integrationType !== 'inbanner') { libUrl = PRIMETIME_URL + getComponentId(bid.params.format) + '.min.js'; - script = getOutstreamScript(bid, size); + script = getOutstreamScript(bid); } else { libUrl = MUSTANG_URL; script = getInBannerScript(bid, size); @@ -326,8 +326,8 @@ export const spec = { } } } - - var location = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.referer : getTopMostWindow().location.href; + // TODO: is 'page' the right value here? + var location = bidderRequest?.refererInfo?.page; if (isValidUrl(location)) { requestParams.loc = location; } diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 17e210d5358..55f04e6b1d3 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -48,9 +48,20 @@ export const ftrackIdSubmodule = { * similar to the module name and ending in id or Id */ decode (value, config) { + if (!value) { return } + const ext = {} + + for (var key in value) { + /** unpack the strings from the arrays */ + ext[key] = value[key][0] + } + return { - ftrackId: value - }; + ftrackId: { + uid: value.DeviceID && value.DeviceID[0], + ext + } + } }, /** @@ -60,13 +71,13 @@ export const ftrackIdSubmodule = { * @param {SubmoduleConfig} config * @param {ConsentData} consentData * @param {(Object|undefined)} cacheIdObj - * @returns {IdResponse|undefined} + * @returns {IdResponse|undefined} A response object that contains id and/or callback. */ getId (config, consentData, cacheIdObj) { if (this.isConfigOk(config) === false || this.isThereConsent(consentData) === false) return undefined; return { - callback: function () { + callback: function (cb) { window.D9v = { UserID: '99999999999999', CampID: '3175', @@ -82,6 +93,8 @@ export const ftrackIdSubmodule = { storage.setDataInLocalStorage(`${FTRACK_PRIVACY_STORAGE_NAME}`, JSON.stringify(consentInfo)); }; + if (typeof cb === 'function') cb(response); + return response; } }; diff --git a/modules/ftrackIdSystem.md b/modules/ftrackIdSystem.md index 0c92f5afab1..24a8dbd08b6 100644 --- a/modules/ftrackIdSystem.md +++ b/modules/ftrackIdSystem.md @@ -80,4 +80,18 @@ You may request by emailing [mailto:privacy@flashtalking.com](privacy@flashtalki #### GDPR -In its current state, Flashtalking’s FTrack Identity Framework User ID Module does not create an ID if a user's consentData is "truthy" (true, 1). In other words, if GDPR applies in any way to a user, FTrack does not create an ID. \ No newline at end of file +In its current state, Flashtalking’s FTrack Identity Framework User ID Module does not create an ID if a user's consentData is "truthy" (true, 1). In other words, if GDPR applies in any way to a user, FTrack does not create an ID. + +--- + +### If you are using pbjs.getUserIdsAsEids(): + +Please note that the `uids` value is a stringified object of the IDs so publishers will need to `JSON.parse()` the value in order to use it: + +``` +{ + "HHID": [""], + "DeviceID": [""], + "SingleDeviceID": ["USERS SINGLE DEVICE ID"] +} +``` \ No newline at end of file diff --git a/modules/futureads.md b/modules/futureads.md deleted file mode 100644 index 7b1c1d55b7f..00000000000 --- a/modules/futureads.md +++ /dev/null @@ -1,48 +0,0 @@ -# Overview -Module Name: Future Ads Bidder Adapter -Module Type: Bidder Adapter -Maintainer: contact@futureads.io -# Description -Connects to Future Ads demand source to fetch bids. -Banner and Video formats are supported. -Please use ```futureads``` as the bidder code. -# Test Parameters -``` -var adUnits = [ - { - code: 'desktop-banner-ad-div', - sizes: [[300, 250]], // a display size - bids: [ - { - bidder: "futureads", - params: { - zone: '2eb6bd58-865c-47ce-af7f-a918108c3fd2' - } - } - ] - },{ - code: 'mobile-banner-ad-div', - sizes: [[300, 50]], // a mobile size - bids: [ - { - bidder: "futureads", - params: { - zone: '62211486-c50b-4356-9f0f-411778d31fcc' - } - } - ] - },{ - code: 'video-ad', - sizes: [[300, 50]], - mediaType: 'video', - bids: [ - { - bidder: "futureads", - params: { - zone: 'ebeb1e79-8cb4-4473-b2d0-2e24b7ff47fd' - } - } - ] - }, -]; -``` diff --git a/modules/fyberBidAdapter.md b/modules/fyberBidAdapter.md deleted file mode 100644 index c394addadfe..00000000000 --- a/modules/fyberBidAdapter.md +++ /dev/null @@ -1,56 +0,0 @@ -# Overview - -``` -Module Name: Fyber Bidder Adapter -Module Type: Bidder Adapter -Maintainer: uri@inner-active.com -``` - -# Description - -Module that connects to Fyber's demand sources - -# Test Parameters -``` -var adUnits = [ -{ -code: 'test-div', -mediaTypes: { -banner: { -sizes: [[300, 250]], // a display rectangle size -} -}, -bids: [ -{ -bidder: 'fyber', - params: { - APP_ID: 'MyCompany_MyApp', - spotType: 'rectangle', - customParams: { - portal: 7002 - } - } -} -] -},{ -code: 'test-div', -mediaTypes: { -banner: { -sizes: [[320, 50]], // a banner size -} -}, -bids: [ -{ -bidder: 'fyber', - params: { - APP_ID: 'MyCompany_MyApp', - spotType: 'banner', - customParams: { - portal: 7001 - } - } -} -] -} -]; -``` diff --git a/modules/gammaBidAdapter.js b/modules/gammaBidAdapter.js index 3e1298b7e23..279eb78812e 100644 --- a/modules/gammaBidAdapter.js +++ b/modules/gammaBidAdapter.js @@ -27,7 +27,7 @@ export const spec = { */ buildRequests: function(bidRequests, bidderRequest) { const serverRequests = []; - const bidderRequestReferer = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || ''; + const bidderRequestReferer = bidderRequest?.refererInfo?.page || ''; for (var i = 0, len = bidRequests.length; i < len; i++) { const gaxObjParams = bidRequests[i]; serverRequests.push({ diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index 22a70db0fab..ae320db1251 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -12,7 +12,6 @@ import { logWarn } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; @@ -34,11 +33,6 @@ export const helper = { startsWith: function (str, search) { return str.substr(0, search.length) === search; }, - getTopWindowDomain: function (url) { - const domainStart = url.indexOf('://') + '://'.length; - return url.substring(domainStart, url.indexOf('/', domainStart) < 0 ? url.length : url.indexOf('/', domainStart)); - }, - getMediaType: function (bid) { if (bid.ext) { if (bid.ext.media_type) { @@ -89,14 +83,12 @@ export const spec = { const {adUnitCode, auctionId, mediaTypes, params, sizes, transactionId} = bidRequest; const baseEndpoint = params['rtbEndpoint'] || ENDPOINTS['gamoshi']; const rtbEndpoint = `${baseEndpoint}/r/${params.supplyPartnerId}/bidr?rformat=open_rtb&reqformat=rtb_json&bidder=prebid` + (params.query ? '&' + params.query : ''); - let url = config.getConfig('pageUrl') || bidderRequest.refererInfo.referer; - const rtbBidRequest = { id: auctionId, site: { - domain: helper.getTopWindowDomain(url), - page: url, - ref: bidderRequest.refererInfo.referer + domain: bidderRequest.refererInfo.domain, + page: bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref }, device: { ua: navigator.userAgent, @@ -127,7 +119,7 @@ export const spec = { const imp = { id: transactionId, - instl: params.instl === 1 ? 1 : 0, + instl: deepAccess(bidderRequest.ortb2Imp, 'instl') === 1 || params.instl === 1 ? 1 : 0, tagid: adUnitCode, bidfloor: helper.getBidFloor(bidRequest) || 0, bidfloorcur: 'USD', @@ -143,7 +135,7 @@ export const spec = { banner: { w: sizes.length ? sizes[0][0] : 300, h: sizes.length ? sizes[0][1] : 250, - pos: params.pos || 0, + pos: deepAccess(bidderRequest, 'mediaTypes.banner.pos') || params.pos || 0, topframe: inIframe() ? 0 : 1 } }); @@ -157,7 +149,7 @@ export const spec = { const videoImp = Object.assign({}, imp, { video: { protocols: bidRequest.mediaTypes.video.protocols || params.protocols || [1, 2, 3, 4, 5, 6], - pos: params.pos || 0, + pos: deepAccess(bidRequest, 'mediaTypes.video.pos') || params.pos || 0, ext: { context: mediaTypes.video.context }, @@ -267,7 +259,7 @@ export const spec = { let gdpr = gdprApplies ? 1 : 0; if (gdprApplies && gdprConsent.consentString) { - consentString = encodeURIComponent(gdprConsent.consentString) + consentString = encodeURIComponent(gdprConsent.consentString); } if (uspConsent) { diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 4a54367a2a2..b7035c509b6 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -12,14 +12,21 @@ import {validateStorageEnforcement} from '../src/storageManager.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; +// modules for which vendor consent is not needed (see https://github.com/prebid/Prebid.js/issues/8161) +const VENDORLESS_MODULES = new Set([ + 'sharedId', + 'pubCommonId', + 'pubProvidedId', +]); + +export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement'; + const TCF2 = { 'purpose1': { id: 1, name: 'storage' }, 'purpose2': { id: 2, name: 'basicAds' }, 'purpose7': { id: 7, name: 'measurement' } } -const VENDORLESS_MODULE_TYPES = ['fpid-module']; - /* These rules would be used if `consentManagement.gdpr.rules` is undefined by the publisher. */ @@ -45,7 +52,8 @@ const storageBlocked = []; const biddersBlocked = []; const analyticsBlocked = []; -let addedDeviceAccessHook = false; +let hooksAdded = false; +let strictStorageEnforcement = false; // Helps in stubbing these functions in unit tests. export const internal = { @@ -118,6 +126,18 @@ function getGvlidForAnalyticsAdapter(code) { return adapterManager.getAnalyticsAdapter(code) && (adapterManager.getAnalyticsAdapter(code).gvlid || null); } +export function shouldEnforce(consentData, purpose, name) { + if (consentData == null && gdprDataHandler.enabled) { + // there is no consent data, but the GDPR module has been installed and configured + // NOTE: this check is not foolproof, as when Prebid first loads, enforcement hooks have not been attached yet + // This piece of code would not run at all, and `gdprDataHandler.enabled` would be false, until the first + // `setConfig({consentManagement})` + logWarn(`Attempting operation that requires purpose ${purpose} consent while consent data is not available${name ? ` (module: ${name})` : ''}. Assuming no consent was given.`) + return true; + } + return consentData && consentData.gdprApplies; +} + /** * This function takes in a rule and consentData and validates against the consentData provided. Depending on what it returns, * the caller may decide to suppress a TCF-sensitive activity. @@ -125,10 +145,10 @@ function getGvlidForAnalyticsAdapter(code) { * @param {Object} consentData - gdpr consent data * @param {string=} currentModule - Bidder code of the current module * @param {number=} gvlId - GVL ID for the module - * @param {string=} moduleType module type + * @param vendorlessModule a predicate function that takes a module name, and returns true if the module does not need vendor consent * @returns {boolean} */ -export function validateRules(rule, consentData, currentModule, gvlId, moduleType) { +export function validateRules(rule, consentData, currentModule, gvlId, vendorlessModule = VENDORLESS_MODULES.has.bind(VENDORLESS_MODULES)) { const purposeId = TCF2[Object.keys(TCF2).filter(purposeName => TCF2[purposeName].name === rule.purpose)[0]].id; // return 'true' if vendor present in 'vendorExceptions' @@ -141,14 +161,12 @@ export function validateRules(rule, consentData, currentModule, gvlId, moduleTyp const vendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); const liTransparency = deepAccess(consentData, `vendorData.purpose.legitimateInterests.${purposeId}`); - const vendorlessModule = includes(VENDORLESS_MODULE_TYPES, moduleType); - /* Since vendor exceptions have already been handled, the purpose as a whole is allowed if it's not being enforced or the user has consented. Similar with vendors. */ const purposeAllowed = rule.enforcePurpose === false || purposeConsent === true; - const vendorAllowed = rule.enforceVendor === false || vendorConsent === true || vendorlessModule === true; + const vendorAllowed = vendorlessModule(currentModule) || rule.enforceVendor === false || vendorConsent === true; /* Few if any vendors should be declaring Legitimate Interest for Device Access (Purpose 1), but some are claiming @@ -165,50 +183,45 @@ export function validateRules(rule, consentData, currentModule, gvlId, moduleTyp /** * This hook checks whether module has permission to access device or not. Device access include cookie and local storage * @param {Function} fn reference to original function (used by hook logic) + * @param isVendorless if true, do not require vendor consent (for e.g. core modules) * @param {Number=} gvlid gvlid of the module * @param {string=} moduleName name of the module - * @param {string=} moduleType module type + * @param result */ -export function deviceAccessHook(fn, gvlid, moduleName, moduleType, result) { +export function deviceAccessHook(fn, isVendorless, gvlid, moduleName, result, {validate = validateRules} = {}) { result = Object.assign({}, { hasEnforcementHook: true }); if (!hasDeviceAccess()) { logWarn('Device access is disabled by Publisher'); result.valid = false; - fn.call(this, gvlid, moduleName, moduleType, result); + } else if (isVendorless && !strictStorageEnforcement) { + // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set + result.valid = true; } else { const consentData = gdprDataHandler.getConsentData(); - if (consentData && consentData.gdprApplies) { - if (consentData.apiVersion === 2) { - const curBidder = config.getCurrentBidder(); - // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder - if (curBidder && (curBidder != moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { - gvlid = getGvlid(curBidder); - } else { - gvlid = getGvlid(moduleName) || gvlid; - } - const curModule = moduleName || curBidder; - let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid, moduleType); - if (isAllowed) { - result.valid = true; - fn.call(this, gvlid, moduleName, moduleType, result); - } else { - curModule && logWarn(`TCF2 denied device access for ${curModule}`); - result.valid = false; - storageBlocked.push(curModule); - fn.call(this, gvlid, moduleName, moduleType, result); - } + if (shouldEnforce(consentData, 1, moduleName)) { + const curBidder = config.getCurrentBidder(); + // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder + if (curBidder && (curBidder != moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) { + gvlid = getGvlid(curBidder); } else { - // The module doesn't enforce TCF1.1 strings + gvlid = getGvlid(moduleName) || gvlid; + } + const curModule = moduleName || curBidder; + let isAllowed = validate(purpose1Rule, consentData, curModule, gvlid, isVendorless ? () => true : undefined); + if (isAllowed) { result.valid = true; - fn.call(this, gvlid, moduleName, moduleType, result); + } else { + curModule && logWarn(`TCF2 denied device access for ${curModule}`); + result.valid = false; + storageBlocked.push(curModule); } } else { result.valid = true; - fn.call(this, gvlid, moduleName, moduleType, result); } } + fn.call(this, isVendorless, gvlid, moduleName, result); } /** @@ -218,20 +231,15 @@ export function deviceAccessHook(fn, gvlid, moduleName, moduleType, result) { */ export function userSyncHook(fn, ...args) { const consentData = gdprDataHandler.getConsentData(); - if (consentData && consentData.gdprApplies) { - if (consentData.apiVersion === 2) { - const curBidder = config.getCurrentBidder(); - const gvlid = getGvlid(curBidder); - let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); - if (isAllowed) { - fn.call(this, ...args); - } else { - logWarn(`User sync not allowed for ${curBidder}`); - storageBlocked.push(curBidder); - } - } else { - // The module doesn't enforce TCF1.1 strings + const curBidder = config.getCurrentBidder(); + if (shouldEnforce(consentData, 1, curBidder)) { + const gvlid = getGvlid(curBidder); + let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid); + if (isAllowed) { fn.call(this, ...args); + } else { + logWarn(`User sync not allowed for ${curBidder}`); + storageBlocked.push(curBidder); } } else { fn.call(this, ...args); @@ -245,25 +253,20 @@ export function userSyncHook(fn, ...args) { * @param {Object} consentData GDPR consent data */ export function userIdHook(fn, submodules, consentData) { - if (consentData && consentData.gdprApplies) { - if (consentData.apiVersion === 2) { - let userIdModules = submodules.map((submodule) => { - const gvlid = getGvlid(submodule.submodule); - const moduleName = submodule.submodule.name; - let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); - if (isAllowed) { - return submodule; - } else { - logWarn(`User denied permission to fetch user id for ${moduleName} User id module`); - storageBlocked.push(moduleName); - } - return undefined; - }).filter(module => module) - fn.call(this, userIdModules, { ...consentData, hasValidated: true }); - } else { - // The module doesn't enforce TCF1.1 strings - fn.call(this, submodules, consentData); - } + if (shouldEnforce(consentData, 1, 'User ID')) { + let userIdModules = submodules.map((submodule) => { + const gvlid = getGvlid(submodule.submodule); + const moduleName = submodule.submodule.name; + let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid); + if (isAllowed) { + return submodule; + } else { + logWarn(`User denied permission to fetch user id for ${moduleName} User id module`); + storageBlocked.push(moduleName); + } + return undefined; + }).filter(module => module) + fn.call(this, userIdModules, { ...consentData, hasValidated: true }); } else { fn.call(this, submodules, consentData); } @@ -277,26 +280,21 @@ export function userIdHook(fn, submodules, consentData) { */ export function makeBidRequestsHook(fn, adUnits, ...args) { const consentData = gdprDataHandler.getConsentData(); - if (consentData && consentData.gdprApplies) { - if (consentData.apiVersion === 2) { - adUnits.forEach(adUnit => { - adUnit.bids = adUnit.bids.filter(bid => { - const currBidder = bid.bidder; - const gvlId = getGvlid(currBidder); - if (includes(biddersBlocked, currBidder)) return false; - const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId); - if (!isAllowed) { - logWarn(`TCF2 blocked auction for ${currBidder}`); - biddersBlocked.push(currBidder); - } - return isAllowed; - }); + if (shouldEnforce(consentData, 2)) { + adUnits.forEach(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => { + const currBidder = bid.bidder; + const gvlId = getGvlid(currBidder); + if (includes(biddersBlocked, currBidder)) return false; + const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId); + if (!isAllowed) { + logWarn(`TCF2 blocked auction for ${currBidder}`); + biddersBlocked.push(currBidder); + } + return isAllowed; }); - fn.call(this, adUnits, ...args); - } else { - // The module doesn't enforce TCF1.1 strings - fn.call(this, adUnits, ...args); - } + }); + fn.call(this, adUnits, ...args); } else { fn.call(this, adUnits, ...args); } @@ -310,26 +308,21 @@ export function makeBidRequestsHook(fn, adUnits, ...args) { */ export function enableAnalyticsHook(fn, config) { const consentData = gdprDataHandler.getConsentData(); - if (consentData && consentData.gdprApplies) { - if (consentData.apiVersion === 2) { - if (!isArray(config)) { - config = [config] - } - config = config.filter(conf => { - const analyticsAdapterCode = conf.provider; - const gvlid = getGvlid(analyticsAdapterCode); - const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); - if (!isAllowed) { - analyticsBlocked.push(analyticsAdapterCode); - logWarn(`TCF2 blocked analytics adapter ${conf.provider}`); - } - return isAllowed; - }); - fn.call(this, config); - } else { - // This module doesn't enforce TCF1.1 strings - fn.call(this, config); + if (shouldEnforce(consentData, 7, 'Analytics')) { + if (!isArray(config)) { + config = [config] } + config = config.filter(conf => { + const analyticsAdapterCode = conf.provider; + const gvlid = getGvlid(analyticsAdapterCode); + const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid); + if (!isAllowed) { + analyticsBlocked.push(analyticsAdapterCode); + logWarn(`TCF2 blocked analytics adapter ${conf.provider}`); + } + return isAllowed; + }); + fn.call(this, config); } else { fn.call(this, config); } @@ -373,6 +366,7 @@ export function setEnforcementConfig(config) { } else { enforcementRules = rules; } + strictStorageEnforcement = !!deepAccess(config, STRICT_STORAGE_ENFORCEMENT); purpose1Rule = find(enforcementRules, hasPurpose1); purpose2Rule = find(enforcementRules, hasPurpose2); @@ -386,20 +380,32 @@ export function setEnforcementConfig(config) { purpose2Rule = DEFAULT_RULES[1]; } - if (purpose1Rule && !addedDeviceAccessHook) { - addedDeviceAccessHook = true; - validateStorageEnforcement.before(deviceAccessHook, 49); - registerSyncInner.before(userSyncHook, 48); - // Using getHook as user id and gdprEnforcement are both optional modules. Using import will auto include the file in build - getHook('validateGdprEnforcement').before(userIdHook, 47); - } - if (purpose2Rule) { - getHook('makeBidRequests').before(makeBidRequestsHook); + if (!hooksAdded) { + if (purpose1Rule) { + hooksAdded = true; + validateStorageEnforcement.before(deviceAccessHook, 49); + registerSyncInner.before(userSyncHook, 48); + // Using getHook as user id and gdprEnforcement are both optional modules. Using import will auto include the file in build + getHook('validateGdprEnforcement').before(userIdHook, 47); + } + if (purpose2Rule) { + getHook('makeBidRequests').before(makeBidRequestsHook); + } + if (purpose7Rule) { + getHook('enableAnalyticsCb').before(enableAnalyticsHook); + } } +} - if (purpose7Rule) { - getHook('enableAnalyticsCb').before(enableAnalyticsHook); - } +export function uninstall() { + [ + validateStorageEnforcement.getHooks({hook: deviceAccessHook}), + registerSyncInner.getHooks({hook: userSyncHook}), + getHook('validateGdprEnforcement').getHooks({hook: userIdHook}), + getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}), + getHook('enableAnalyticsCb').getHooks({hook: enableAnalyticsHook}), + ].forEach(hook => hook.remove()); + hooksAdded = false; } config.getConfig('consentManagement', config => setEnforcementConfig(config.consentManagement)); diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js index 98659cc25e2..25322d81f9b 100644 --- a/modules/getintentBidAdapter.js +++ b/modules/getintentBidAdapter.js @@ -97,7 +97,7 @@ export const spec = { return bids; } -} +}; function buildUrl(bid) { return 'https://' + BID_HOST + (bid.is_video ? BID_VIDEO_PATH : BID_BANNER_PATH); diff --git a/modules/giantsBidAdapter.md b/modules/giantsBidAdapter.md deleted file mode 100644 index 8d7cdd81184..00000000000 --- a/modules/giantsBidAdapter.md +++ /dev/null @@ -1,30 +0,0 @@ -# Overview - -``` -Module Name: Giants Bid Adapter -Module Type: Bidder Adapter -Maintainer: info@prebid.org -``` - -# Description - -Connects to Giants exchange for bids. - -Giants bid adapter supports Banner. - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'giants', - params: { - zoneId: '584072408' - } - }] - } -]; -``` \ No newline at end of file diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js index 48b2cd43c3b..91ed5c9b3fb 100644 --- a/modules/gjirafaBidAdapter.js +++ b/modules/gjirafaBidAdapter.js @@ -45,7 +45,7 @@ export const spec = { if (!propertyId) { propertyId = bidRequest.params.propertyId; } if (!pageViewGuid && bidRequest.params) { pageViewGuid = bidRequest.params.pageViewGuid || ''; } if (!bidderRequestId) { bidderRequestId = bidRequest.bidderRequestId; } - if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } + if (!url && bidderRequest) { url = bidderRequest.refererInfo.page; } if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } if (Object.keys(data).length === 0 && bidRequest.params.data && Object.keys(bidRequest.params.data).length !== 0) { data = bidRequest.params.data; } @@ -74,7 +74,7 @@ export const spec = { placements: placements, contents: contents, data: data - } + }; return [{ method: 'POST', @@ -113,7 +113,7 @@ export const spec = { } return bidResponses; } -} +}; /** * Generate size param for bid request using sizes array diff --git a/modules/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js index 35aaf56c604..bbb4dbb30cd 100644 --- a/modules/glimpseBidAdapter.js +++ b/modules/glimpseBidAdapter.js @@ -49,7 +49,7 @@ export const spec = { const auth = getVaultJwt(); const referer = getReferer(bidderRequest); const imp = validBidRequests.map(processBidRequest); - const fpd = getFirstPartyData(); + const fpd = getFirstPartyData(bidderRequest.ortb2); const data = { auth, @@ -95,7 +95,8 @@ function getVaultJwt() { } function getReferer(bidderRequest) { - return bidderRequest?.refererInfo?.referer || ''; + // TODO: is 'page' the right value here? + return bidderRequest?.refererInfo?.page || ''; } function buildQuery(bidderRequest) { @@ -160,8 +161,8 @@ function normalizeSizes(sizes) { return sizes; } -function getFirstPartyData() { - let fpd = config.getConfig('ortb2') || {}; +function getFirstPartyData(ortb2) { + let fpd = ortb2 || {}; optimizeObject(fpd); return fpd; } diff --git a/modules/glomexBidAdapter.js b/modules/glomexBidAdapter.js index 5cabd2515a9..32c2036a748 100644 --- a/modules/glomexBidAdapter.js +++ b/modules/glomexBidAdapter.js @@ -26,10 +26,11 @@ export const spec = { data: { auctionId: bidderRequest.auctionId, refererInfo: { + // TODO: this collects everything it finds, except for canonicalUrl isAmp: refererInfo.isAmp, numIframes: refererInfo.numIframes, reachedTop: refererInfo.reachedTop, - referer: refererInfo.referer + referer: refererInfo.topmostLocation, }, gdprConsent: { consentString: gdprConsent.consentString, diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js index 087f74906fb..9bc1a15b60b 100644 --- a/modules/gmosspBidAdapter.js +++ b/modules/gmosspBidAdapter.js @@ -1,7 +1,18 @@ -import { deepAccess, getDNT, getBidIdParameter, tryAppendQueryString, isEmpty, createTrackPixelHtml, logError, deepSetValue, getWindowTop, getWindowLocation } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { + createTrackPixelHtml, + deepAccess, + deepSetValue, + getBidIdParameter, + getDNT, + getWindowTop, + isEmpty, + logError, + tryAppendQueryString +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER} from '../src/mediaTypes.js'; + const BIDDER_CODE = 'gmossp'; const ENDPOINT = 'https://sp.gmossp-sp.jp/hb/prebid/query.ad'; @@ -155,9 +166,10 @@ function getUrlInfo(refererInfo) { } return { - url: getUrl(refererInfo), canonicalLink: canonicalLink, - ref: getReferrer(), + // TODO: are these the right refererInfo values? + url: refererInfo.topmostLocation, + ref: refererInfo.ref || window.document.referrer, }; } @@ -169,24 +181,4 @@ function getMetaElements() { } } -function getUrl(refererInfo) { - if (refererInfo && refererInfo.referer) { - return refererInfo.referer; - } - - try { - return getWindowTop.location.href; - } catch (e) { - return getWindowLocation.href; - } -} - -function getReferrer() { - try { - return getWindowTop.document.referrer; - } catch (e) { - return document.referrer; - } -} - registerBidder(spec); diff --git a/modules/gnetBidAdapter.js b/modules/gnetBidAdapter.js index 274e8db2b50..8bab043d0db 100644 --- a/modules/gnetBidAdapter.js +++ b/modules/gnetBidAdapter.js @@ -31,7 +31,8 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { const bidRequests = []; - const referer = bidderRequest.refererInfo.referer; + // TODO: is 'page' the right value? + const referer = bidderRequest.refererInfo.page; _each(validBidRequests, (request) => { const data = {}; diff --git a/modules/go2net.md b/modules/go2net.md deleted file mode 100644 index acea57b1c55..00000000000 --- a/modules/go2net.md +++ /dev/null @@ -1,29 +0,0 @@ -# Overview - -Module Name: Go2Net Bidder Adapter -Module Type: Bidder Adapter -Maintainer: vprytuzhalova@go2net.com.ua - -# Description - -Connects to Go2Net demand source to fetch bids. -Banner and Video formats are supported. -Please use ```go2net``` as the bidder code. - -# Ad Unit Example -``` - var adUnits = [ - { - code: 'desktop-banner-ad-div', - sizes: [[300, 250]], // a display size - bids: [ - { - bidder: "go2net", - params: { - zone: 'fb3d34d0-7a88-4a4a-a5c9-8088cd7845f4' - } - } - ] - } - ]; -``` diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index 46ae3054188..21c56643353 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -29,6 +29,8 @@ import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {auctionManager} from '../src/auctionManager.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'goldbach'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -112,6 +114,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + let localBidRequests = []; bidRequests.forEach(bid => { if (Array.isArray(bid.params.placementId)) { @@ -202,7 +207,7 @@ export const spec = { payload['iab_support'] = { omidpn: 'Appnexus', omidpv: '$prebid.version$' - } + }; } if (member > 0) { @@ -210,7 +215,7 @@ export const spec = { } if (appDeviceObjBid) { - payload.device = appDeviceObj + payload.device = appDeviceObj; } if (appIdObjBid) { payload.app = appIdObj; @@ -241,16 +246,17 @@ export const spec = { } if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent + payload.us_privacy = bidderRequest.uspConsent; } if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { - rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + // TODO: this collects everything it finds, except for topmostLocation + rd_ref: encodeURIComponent(bidderRequest.refererInfo.topmostLocation), rd_top: bidderRequest.refererInfo.reachedTop, rd_ifs: bidderRequest.refererInfo.numIframes, rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - } + }; payload.referrer_detection = refererinfo; } @@ -267,7 +273,6 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; - addUserId(eids, deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); @@ -436,7 +441,7 @@ export const spec = { reloadViewabilityScriptWithCorrectParameters(bid); } } -} +}; function isPopulatedArray(arr) { return !!(isArray(arr) && arr.length > 0); @@ -454,7 +459,7 @@ function reloadViewabilityScriptWithCorrectParameters(bid) { if (viewJsPayload) { let prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; - let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload) + let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload); let newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); @@ -533,16 +538,6 @@ function getViewabilityScriptUrlFromPayload(viewJsPayload) { return jsTrackerSrc; } -function hasPurpose1Consent(bidderRequest) { - let result = true; - if (bidderRequest && bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { - result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); - } - } - return result; -} - function formatRequest(payload, bidderRequest) { let request = []; let options = { @@ -551,14 +546,14 @@ function formatRequest(payload, bidderRequest) { let endpointUrl = URL; - if (!hasPurpose1Consent(bidderRequest)) { + if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { endpointUrl = URL_SIMPLE; } if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { options.customHeaders = { 'X-Is-Test': 1 - } + }; } if (payload.tags.length > MAX_IMPS_PER_REQUEST) { @@ -771,7 +766,7 @@ function bidToTag(bid) { tag.code = bid.params.invCode; } tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.use_pmt_rule = bid.params.usePaymentRule || false; tag.prebid = true; tag.disable_psa = true; let bidFloor = getBidFloor(bid); diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js index 1993f0c9b64..6b549f347bb 100644 --- a/modules/gothamadsBidAdapter.js +++ b/modules/gothamadsBidAdapter.js @@ -2,6 +2,7 @@ import { logMessage, deepSetValue, deepAccess, _map, logWarn } from '../src/util import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'gothamads'; const ACCOUNTID_MACROS = '[account_id]'; @@ -68,14 +69,18 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + if (validBidRequests && validBidRequests.length === 0) return [] let accuontId = validBidRequests[0].params.accountId; const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); let winTop = window; let location; + // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin try { - location = new URL(bidderRequest.refererInfo.referer) + location = new URL(bidderRequest.refererInfo.page) winTop = window.top; } catch (e) { location = winTop.location; @@ -134,7 +139,7 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: (serverResponse) => { - if (!serverResponse || !serverResponse.body) return [] + if (!serverResponse || !serverResponse.body) return []; let GothamAdsResponse = serverResponse.body; let bids = []; @@ -268,7 +273,7 @@ const addNativeParameters = bidRequest => { hmin = sizes[1]; } - asset[props.name] = {} + asset[props.name] = {}; if (bidParams.len) asset[props.name]['len'] = bidParams.len; if (props.type) asset[props.name]['type'] = props.type; diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 993ddd927b0..d1743ac63af 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -25,11 +25,21 @@ const LOG_ERROR_MESS = { hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' }; +const ALIAS_CONFIG = { + 'trustx': { + endpoint: 'https://grid.bidswitch.net/hbjson?sp=trustx', + syncurl: 'https://x.bidswitch.net/sync?ssp=themediagrid', + bidResponseExternal: { + netRevenue: false + } + } +}; + let hasSynced = false; export const spec = { code: BIDDER_CODE, - aliases: ['playwire', 'adlivetech'], + aliases: ['playwire', 'adlivetech', 'trustx'], supportedMediaTypes: [ BANNER, VIDEO ], /** * Determines whether or not the given bid request is valid. @@ -58,9 +68,12 @@ export const spec = { let userIdAsEids = null; let user = null; let userExt = null; + let endpoint = null; + let forceBidderName = false; let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; - const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; + // TODO: is 'page' the right value here? + const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; const imp = []; const bidsMap = {}; @@ -77,7 +90,10 @@ export const spec = { if (!userIdAsEids) { userIdAsEids = bid.userIdAsEids; } - const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp} = bid; + if (!endpoint) { + endpoint = ALIAS_CONFIG[bid.bidder] && ALIAS_CONFIG[bid.bidder].endpoint; + } + const { params: { uid, keywords, forceBidder }, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp } = bid; bidsMap[bidId] = bid; const bidFloor = _getFloor(mediaTypes || {}, bid); const jwTargeting = rtd && rtd.jwplayer && rtd.jwplayer.targeting; @@ -136,8 +152,19 @@ export const spec = { if (impObj.banner || impObj.video) { imp.push(impObj); } + + if (!forceBidderName && forceBidder && ALIAS_CONFIG[forceBidder]) { + forceBidderName = forceBidder; + } }); + forceBidderName = config.getConfig('forceBidderName') || forceBidderName; + + if (forceBidderName && ALIAS_CONFIG[forceBidderName]) { + endpoint = ALIAS_CONFIG[forceBidderName].endpoint; + this.forceBidderName = forceBidderName; + } + const source = { tid: auctionId && auctionId.toString(), ext: { @@ -176,7 +203,7 @@ export const spec = { }; } - const ortb2UserData = config.getConfig('ortb2.user.data'); + const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); if (ortb2UserData && ortb2UserData.length) { if (!user) { user = { data: [] }; @@ -190,7 +217,7 @@ export const spec = { userExt = {consent: gdprConsent.consentString}; } - const ortb2UserExtDevice = config.getConfig('ortb2.user.ext.device'); + const ortb2UserExtDevice = deepAccess(bidderRequest, 'ortb2.user.ext.device'); if (ortb2UserExtDevice) { userExt = userExt || {}; userExt.device = { ...ortb2UserExtDevice }; @@ -217,8 +244,8 @@ export const spec = { request.user = user; } - const userKeywords = deepAccess(config.getConfig('ortb2.user'), 'keywords') || null; - const siteKeywords = deepAccess(config.getConfig('ortb2.site'), 'keywords') || null; + const userKeywords = deepAccess(bidderRequest, 'ortb2.user.keywords') || null; + const siteKeywords = deepAccess(bidderRequest, 'ortb2.site.keywords') || null; if (userKeywords) { pageKeywords = pageKeywords || {}; @@ -255,7 +282,7 @@ export const spec = { ext: { gdpr: gdprConsent.gdprApplies ? 1 : 0 } - } + }; } if (uspConsent) { @@ -272,7 +299,7 @@ export const spec = { request.regs.coppa = 1; } - const site = config.getConfig('ortb2.site'); + const site = deepAccess(bidderRequest, 'ortb2.site'); if (site) { const pageCategory = [...(site.cat || []), ...(site.pagecat || [])].filter((category) => { return category && typeof category === 'string' @@ -297,9 +324,8 @@ export const spec = { return { method: 'POST', - url: ENDPOINT_URL, + url: endpoint || ENDPOINT_URL, data: JSON.stringify(request), - newFormat: true, bidsMap }; }, @@ -308,9 +334,10 @@ export const spec = { * * @param {*} serverResponse A successful response from the server. * @param {*} bidRequest + * @param {*} RendererConst * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function(serverResponse, bidRequest) { + interpretResponse: function(serverResponse, bidRequest, RendererConst = Renderer) { serverResponse = serverResponse && serverResponse.body; const bidResponses = []; @@ -321,15 +348,18 @@ export const spec = { errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; } + const bidderCode = this.forceBidderName || this.code; + if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses); + _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst, bidderCode); }); } if (errorMessage) logError(errorMessage); return bidResponses; }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + getUserSyncs: function (...args) { + const [syncOptions,, gdprConsent, uspConsent] = args; if (!hasSynced && syncOptions.pixelEnabled) { let params = ''; @@ -345,10 +375,13 @@ export const spec = { params += `&us_privacy=${uspConsent}`; } + const bidderCode = this.forceBidderName || this.code; + const syncUrl = (ALIAS_CONFIG[bidderCode] && ALIAS_CONFIG[bidderCode].syncurl) || SYNC_URL; + hasSynced = true; return { type: 'image', - url: SYNC_URL + params + url: syncUrl + params }; } } @@ -392,7 +425,7 @@ function _getBidFromResponse(respItem) { return respItem && respItem.bid && respItem.bid[0]; } -function _addBidResponse(serverBid, bidRequest, bidResponses) { +function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bidderCode) { if (!serverBid) return; let errorMessage; if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); @@ -434,13 +467,14 @@ function _addBidResponse(serverBid, bidRequest, bidResponses) { bidResponse.renderer = createRenderer(bidResponse, { id: bid.bidId, url: RENDERER_URL - }); + }, RendererConst); } } else { bidResponse.ad = serverBid.adm; bidResponse.mediaType = BANNER; } - bidResponses.push(bidResponse); + const bidResponseExternal = (ALIAS_CONFIG[bidderCode] && ALIAS_CONFIG[bidderCode].bidResponseExternal) || {}; + bidResponses.push(mergeDeep(bidResponse, bidResponseExternal)); } } if (errorMessage) { @@ -568,8 +602,8 @@ function outstreamRender (bid) { }); } -function createRenderer (bid, rendererParams) { - const renderer = Renderer.install({ +function createRenderer (bid, rendererParams, RendererConst) { + const renderer = RendererConst.install({ id: rendererParams.id, url: rendererParams.url, loaded: false diff --git a/modules/gridNMBidAdapter.js b/modules/gridNMBidAdapter.js index 3c46b25b8e1..bfe5f3952bc 100644 --- a/modules/gridNMBidAdapter.js +++ b/modules/gridNMBidAdapter.js @@ -1,4 +1,13 @@ -import { isStr, deepAccess, isArray, isNumber, logError, logWarn, parseGPTSingleSizeArrayToRtbSize } from '../src/utils.js'; +import { + isStr, + deepAccess, + isArray, + isNumber, + logError, + logWarn, + parseGPTSingleSizeArrayToRtbSize, + mergeDeep +} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { VIDEO } from '../src/mediaTypes.js'; @@ -67,7 +76,7 @@ export const spec = { const requests = []; let { bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo } = bidderRequest || {}; - const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; + const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; bids.forEach(bid => { let user; @@ -153,9 +162,7 @@ export const spec = { user = { data: [{ name: 'iow_labs_pub_data', - segment: jwpseg.map((seg) => { - return {name: 'jwpseg', value: seg}; - }) + segment: segmentProcessing(jwpseg, 'jwpseg'), }] }; } @@ -174,16 +181,34 @@ export const spec = { user.ext = userExt; } + const ortb2UserData = deepAccess(bidderRequest, 'ortb2.user.data'); + if (ortb2UserData && ortb2UserData.length) { + if (!user) { + user = { data: [] }; + } + user = mergeDeep(user, { + data: [...ortb2UserData] + }); + } + if (user) { request.user = user; } + const site = deepAccess(bidderRequest, 'ortb2.site'); + if (site) { + const data = deepAccess(site, 'content.data'); + if (data && data.length) { + const siteContent = request.site.content || {}; + request.site.content = mergeDeep(siteContent, { data }); + } + } if (gdprConsent && gdprConsent.gdprApplies) { request.regs = { ext: { gdpr: gdprConsent.gdprApplies ? 1 : 0 } - } + }; } if (uspConsent) { @@ -408,4 +433,20 @@ export function getSyncUrl() { return SYNC_URL; } +function segmentProcessing(segment, forceSegName) { + return segment + .map((seg) => { + const value = seg && (seg.value || seg.id || seg); + if (typeof value === 'string' || typeof value === 'number') { + return { + value: value.toString(), + ...(forceSegName && { name: forceSegName }), + ...(seg.name && { name: seg.name }), + }; + } + return null; + }) + .filter((seg) => !!seg); +} + registerBidder(spec); diff --git a/modules/growadvertisingBidAdapter.js b/modules/growadvertisingBidAdapter.js index 286d27607c5..b9b256dbaff 100644 --- a/modules/growadvertisingBidAdapter.js +++ b/modules/growadvertisingBidAdapter.js @@ -3,6 +3,7 @@ import { getBidIdParameter, deepAccess, _each, triggerPixel } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'growads'; @@ -15,6 +16,9 @@ export const spec = { }, buildRequests: function (validBidRequests) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let zoneId; let domain; let requestURI; @@ -101,7 +105,8 @@ export const spec = { netRevenue: true, ttl: response.ttl, adUnitCode: request.adUnitCode, - referrer: deepAccess(request, 'refererInfo.referer') + // TODO: is 'page' the right value here? + referrer: deepAccess(request, 'refererInfo.page') }; if (response.hasOwnProperty(NATIVE)) { diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index f7662f54fae..fe15602c0c0 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -294,7 +294,7 @@ function buildRequests(validBidRequests, bidderRequest) { const gdprConsent = bidderRequest && bidderRequest.gdprConsent; const uspConsent = bidderRequest && bidderRequest.uspConsent; const timeout = config.getConfig('bidderTimeout'); - const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page; _each(validBidRequests, bidRequest => { const { bidId, @@ -321,7 +321,7 @@ function buildRequests(validBidRequests, bidderRequest) { data.to = to; // ADTS-169 add adUnitCode to requests - if (adUnitCode) data.aun = adUnitCode + if (adUnitCode) data.aun = adUnitCode; // ADTS-134 Retrieve ID envelopes for (const eid in eids) data[eid] = eids[eid]; @@ -370,9 +370,11 @@ function buildRequests(validBidRequests, bidderRequest) { data.pi = 5; } else if (mediaTypes.video) { data.pi = mediaTypes.video.linearity === 2 ? 6 : 7; // invideo : video + } else if (params.product && params.product.toLowerCase() === 'skins') { + data.pi = 8; } } else { // legacy params - data = { ...data, ...handleLegacyParams(params, sizes) } + data = { ...data, ...handleLegacyParams(params, sizes) }; } if (gdprConsent) { @@ -398,7 +400,7 @@ function buildRequests(validBidRequests, bidderRequest) { url: BID_ENDPOINT, method: 'GET', data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId)) - }) + }); }); return bids; } diff --git a/modules/gumgumBidAdapter.md b/modules/gumgumBidAdapter.md index 57d56235d1c..88c9cd29f69 100644 --- a/modules/gumgumBidAdapter.md +++ b/modules/gumgumBidAdapter.md @@ -10,7 +10,7 @@ Maintainer: engineering@gumgum.com GumGum adapter for Prebid.js Please note that both video and in-video products require a mediaType of video. -In-screen and slot products should have a mediaType of banner. +In-screen, slot, and skins products should have a mediaType of banner. # Test Parameters ``` @@ -50,6 +50,24 @@ var adUnits = [ } } ] + },{ + code: 'skins-placement', + sizes: [[300, 50]], + mediaTypes: { + banner: { + sizes: [[1, 1]], + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'dc9d6be1', // GumGum Zone ID given to the client + product: 'skins', + bidfloor: 0.03 // CPM bid floor + } + } + ] },{ code: 'video-placement', sizes: [[300, 50]], diff --git a/modules/gxoneBidAdapter.md b/modules/gxoneBidAdapter.md deleted file mode 100644 index 3168d297da3..00000000000 --- a/modules/gxoneBidAdapter.md +++ /dev/null @@ -1,40 +0,0 @@ -# Overview - -Module Name: GXOne Bidder Adapter -Module Type: Bidder Adapter -Maintainer: olivier@geronimo.co - -# Description - -Module that connects to GXOne demand source to fetch bids. - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: "gxone", - params: { - uid: '2', - priceType: 'gross' // by default is 'net' - } - } - ] - },{ - code: 'test-div', - sizes: [[728, 90]], - bids: [ - { - bidder: "gxone", - params: { - uid: 9, - priceType: 'gross' - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/h12mediaBidAdapter.js b/modules/h12mediaBidAdapter.js index 9a6244a9e82..c58f15c9a81 100644 --- a/modules/h12mediaBidAdapter.js +++ b/modules/h12mediaBidAdapter.js @@ -70,8 +70,9 @@ export const spec = { gdpr_cs: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), usp: !!deepAccess(bidderRequest, 'uspConsent', false), usp_cs: deepAccess(bidderRequest, 'uspConsent', ''), - topLevelUrl: deepAccess(bidderRequest, 'refererInfo.referer', ''), - refererUrl: windowTop.document.referrer, + topLevelUrl: deepAccess(bidderRequest, 'refererInfo.page', ''), + // TODO: does the fallback make sense here? + refererUrl: deepAccess(bidderRequest, 'refererInfo.ref', window.document.referrer), isiframe, version: '$prebid.version$', ExtUserIDs: bidRequest.userId, @@ -197,7 +198,7 @@ function getIsHidden(elem) { } catch (o) { return false; } - } while ((m < 250) && (lastElem != null) && (elemHidden === false)) + } while ((m < 250) && (lastElem != null) && (elemHidden === false)); return elemHidden; } diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index bc112989426..52829cf754d 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -1,5 +1,5 @@ import { ajax } from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; import CONSTANTS from '../src/constants.json'; @@ -10,7 +10,7 @@ import {getRefererInfo} from '../src/refererDetection.js'; * hadronAnalyticsAdapter.js - Audigent Hadron Analytics Adapter */ -const HADRON_ANALYTICS_URL = 'https://analytics.hadron.ad.gt/api/v1/analytics' +const HADRON_ANALYTICS_URL = 'https://analytics.hadron.ad.gt/api/v1/analytics'; const HADRONID_ANALYTICS_VER = 'pbadgt0'; const DEFAULT_PARTNER_ID = 0; const AU_GVLID = 561; @@ -36,10 +36,7 @@ var pageView = { timezoneOffset: new Date().getTimezoneOffset(), language: window.navigator.language, vendor: window.navigator.vendor, - pageUrl: (() => { - const ri = getRefererInfo(); - return ri.canonicalUrl || ri.referer; - })(), + pageUrl: getRefererInfo().page, screenWidth: x, screenHeight: y }; @@ -155,7 +152,7 @@ hadronAnalyticsAdapter.enableAnalytics = function(conf = {}) { } hadronAnalyticsAdapter.originEnableAnalytics(conf); -} +}; function flush() { // Don't send anything if no partner id was declared diff --git a/modules/hadronAnalyticsAdapter.md b/modules/hadronAnalyticsAdapter.md index f549cf502b2..8a41be8d36e 100644 --- a/modules/hadronAnalyticsAdapter.md +++ b/modules/hadronAnalyticsAdapter.md @@ -33,7 +33,7 @@ The following configuration parameters are available: ```javascript pbjs.enableAnalytics({ - provider: 'hadron', + provider: 'hadronAnalytics', options: { partnerId: 1234, // change to the Partner ID you got from Audigent eventsToTrack: ['auctionEnd','bidWon'] @@ -41,8 +41,8 @@ pbjs.enableAnalytics({ }); ``` -| Parameter | Scope | Type | Description | Example | -| --- | --- | --- |---------------------------------------------------------| --- | -| provider | Required | String | The name of this module: `hadronAnalytics` | `hadronAnalytics` | -| options.partnerId | Required | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` | +| Parameter | Scope | Type | Description | Example | +|-----------------------|----------|------------------|---------------------------------------------------------|---------------------------| +| provider | Required | String | The name of this module: `hadronAnalytics` | `hadronAnalytics` | +| options.partnerId | Required | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` | | options.eventsToTrack | Optional | Array of strings | Overrides the set of tracked events | `['auctionEnd','bidWon']` | diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index db2620d2422..2f10245cd59 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -12,6 +12,7 @@ import { isFn, isStr, isPlainObject, logError } from '../src/utils.js'; const MODULE_NAME = 'hadronId'; const AU_GVLID = 561; +const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'hadron'}); @@ -29,6 +30,15 @@ function paramOrDefault(param, defaultVal, arg) { return defaultVal; } +/** + * @param {string} url + * @param {string} params + * @returns {string} + */ +const urlAddParams = (url, params) => { + return url + (url.indexOf('?') > -1 ? '&' : '?') + params +} + /** @type {Submodule} */ export const hadronIdSubmodule = { /** @@ -39,8 +49,8 @@ export const hadronIdSubmodule = { /** * decode the stored id value for passing to bid requests * @function - * @param {{value:string}} value - * @returns {{hadronId:Object}} + * @param {string} value + * @returns {Object} */ decode(value) { let hadronId = storage.getDataFromLocalStorage('auHadronId'); @@ -59,9 +69,12 @@ export const hadronIdSubmodule = { if (!isPlainObject(config.params)) { config.params = {}; } - const url = paramOrDefault(config.params.url, - `https://id.hadron.ad.gt/api/v1/pbhid`, - config.params.urlArg); + const partnerId = config.params.partnerId | 0; + + const url = urlAddParams( + paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), + `partner_id=${partnerId}&_it=prebid` + ); const resp = function (callback) { let hadronId = storage.getDataFromLocalStorage('auHadronId'); diff --git a/modules/hadronIdSystem.md b/modules/hadronIdSystem.md index 26539676e17..7521cca06ac 100644 --- a/modules/hadronIdSystem.md +++ b/modules/hadronIdSystem.md @@ -11,6 +11,9 @@ pbjs.setConfig({ usersync: { userIds: [{ name: 'hadronId', + params: { + partnerId: 1234 // change it to the Partner ID you'll get from Audigent + }, storage: { name: 'hadronId', type: 'html5' @@ -22,14 +25,15 @@ pbjs.setConfig({ ## Parameter Descriptions for the `usersync` Configuration Section The below parameters apply only to the HadronID User ID Module integration. -| Param under usersync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | ID value for the HadronID module - `"hadronId"` | `"hadronId"` | -| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | -| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | -| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"hadronid"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` | -| value | Optional | Object | Used only if the page has a separate mechanism for storing the Hadron ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"hadronId": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}` | -| params | Optional | Object | Used to store params for the id system | -| params.url | Optional | String | Set an alternate GET url for HadronId with this parameter | -| params.urlArg | Optional | Object | Optional url parameter for params.url | +| Param under usersync.userIds[] | Scope | Type | Description | Example | +|--------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------| +| name | Required | String | ID value for the HadronID module - `"hadronId"` | `"hadronId"` | +| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | +| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | +| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"hadronid"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` | +| value | Optional | Object | Used only if the page has a separate mechanism for storing the Hadron ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"hadronId": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}` | +| params | Optional | Object | Used to store params for the id system | +| params.partnerId | Required | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` | +| params.url | Optional | String | Set an alternate GET url for HadronId with this parameter | +| params.urlArg | Optional | Object | Optional url parameter for params.url | diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index a99edbe6951..0bd4e6f8344 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -23,6 +23,15 @@ export const HALOID_LOCAL_NAME = 'auHadronId'; export const RTD_LOCAL_NAME = 'auHadronRtd'; export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: SUBMODULE_NAME}); +/** + * @param {string} url + * @param {string} params + * @returns {string} + */ +const urlAddParams = (url, params) => { + return url + (url.indexOf('?') > -1 ? '&' : '?') + params +}; + /** * Deep set an object unless value present. * @param {Object} obj @@ -118,27 +127,13 @@ export function addRealTimeData(bidConfig, rtd, rtdConfig) { if (rtdConfig.params && rtdConfig.params.handleRtd) { rtdConfig.params.handleRtd(bidConfig, rtd, rtdConfig, config); } else { + // TODO: this and haloRtdProvider are a copy-paste of each other if (isPlainObject(rtd.ortb2)) { - let ortb2 = config.getConfig('ortb2') || {}; - config.setConfig({ortb2: mergeLazy(ortb2, rtd.ortb2)}); + mergeLazy(bidConfig.ortb2Fragments?.global, rtd.ortb2); } if (isPlainObject(rtd.ortb2b)) { - let bidderConfig = config.getBidderConfig(); - - Object.keys(rtd.ortb2b).forEach(bidder => { - let rtdOptions = rtd.ortb2b[bidder] || {}; - - let bidderOptions = {}; - if (isPlainObject(bidderConfig[bidder])) { - bidderOptions = bidderConfig[bidder]; - } - - config.setBidderConfig({ - bidders: [bidder], - config: mergeLazy(bidderOptions, rtdOptions) - }); - }); + mergeLazy(bidConfig.ortb2Fragments?.bidder, Object.fromEntries(Object.entries(rtd.ortb2b).map(([_, cfg]) => [_, cfg.ortb2]))); } } } @@ -165,11 +160,13 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { } } - const userIds = (getGlobal()).getUserIds(); + const userIds = typeof getGlobal().getUserIds === 'function' ? (getGlobal()).getUserIds() : {}; let hadronId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); if (isStr(hadronId)) { - (getGlobal()).refreshUserIds({submoduleNames: 'hadronId'}); + if (typeof getGlobal().refreshUserIds === 'function') { + (getGlobal()).refreshUserIds({submoduleNames: 'hadronId'}); + } userIds.hadronId = hadronId; getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); } else { @@ -177,8 +174,12 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { userIds.hadronId = hadronId; getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); } + const partnerId = rtdConfig.params.partnerId | 0; const hadronIdUrl = rtdConfig.params && rtdConfig.params.hadronIdUrl; - const scriptUrl = paramOrDefault(hadronIdUrl, HADRON_ID_DEFAULT_URL, userIds); + const scriptUrl = urlAddParams( + paramOrDefault(hadronIdUrl, HADRON_ID_DEFAULT_URL, userIds), + `partner_id=${partnerId}&_it=prebid` + ); loadExternalScript(scriptUrl, 'hadron', () => { logInfo(LOG_PREFIX, 'hadronIdTag loaded', scriptUrl); }) @@ -197,7 +198,7 @@ export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, let reqParams = {}; if (isPlainObject(rtdConfig)) { - set(rtdConfig, 'params.requestParams.ortb2', config.getConfig('ortb2')); + set(rtdConfig, 'params.requestParams.ortb2', bidConfig.ortb2Fragments.global); reqParams = rtdConfig.params.requestParams; } diff --git a/modules/hadronRtdProvider.md b/modules/hadronRtdProvider.md index 0dbe9666230..5064e75dde0 100644 --- a/modules/hadronRtdProvider.md +++ b/modules/hadronRtdProvider.md @@ -44,8 +44,9 @@ pbjs.setConfig( params: { segmentCache: false, requestParams: { - publisherId: 1234 - } + publisherId: 1234 // deprecated, use partnerId instead + }, + partnerId: 1234 } } ] @@ -56,15 +57,17 @@ pbjs.setConfig( ### Parameter Descriptions for the Hadron Configuration Section -| Name |Type | Description | Notes | -| :------------ | :------------ | :------------ |:------------ | -| name | String | Real time data module name | Always 'hadron' | -| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | -| params | Object | | | -| params.handleRtd | Function | A passable RTD handler that allows custom adunit and ortb2 logic to be configured. The function signature is (bidConfig, rtd, rtdConfig, pbConfig) => {}. | Optional | -| params.segmentCache | Boolean | This parameter tells the Hadron RTD module to attempt reading segments from a local storage cache instead of always requesting them from the Audigent server. | Optional. Defaults to false. | -| params.requestParams | Object | Publisher partner specific configuration options, such as optional publisher id and other segment query related metadata to be submitted to Audigent's backend with each request. Contact prebid@audigent.com for more information. | Optional | -| params.hadronIdUrl | String | Parameter to specify alternate hadronid endpoint url. | Optional | +| Name | Type | Description | Notes | +|:---------------------------------|:---------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------| +| name | String | Real time data module name | Always 'hadron' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | | +| params.partnerId | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` | +| params.handleRtd | Function | A passable RTD handler that allows custom adunit and ortb2 logic to be configured. The function signature is (bidConfig, rtd, rtdConfig, pbConfig) => {}. | Optional | +| params.segmentCache | Boolean | This parameter tells the Hadron RTD module to attempt reading segments from a local storage cache instead of always requesting them from the Audigent server. | Optional. Defaults to false. | +| params.requestParams | Object | Publisher partner specific configuration options, such as optional publisher id and other segment query related metadata to be submitted to Audigent's backend with each request. Contact prebid@audigent.com for more information. | Optional | +| params.requestParams.publisherId | Object | (deprecated) Publisher id and other segment query related metadata to be submitted to Audigent's backend with each request. Contact prebid@audigent.com for more information. | Optional | +| params.hadronIdUrl | String | Parameter to specify alternate hadronid endpoint url. | Optional | ### Publisher Customized RTD Handling As indicated above, it is possible to provide your own bid augmentation @@ -100,8 +103,9 @@ pbjs.setConfig( }, segmentCache: false, requestParams: { - publisherId: 1234 - } + publisherId: 1234 // deprecated, use partnerId instead + }, + partnerId: 1234 } } ] diff --git a/modules/haloIdSystem.js b/modules/haloIdSystem.js deleted file mode 100644 index 2ce18e1e740..00000000000 --- a/modules/haloIdSystem.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * This module adds HaloID to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/haloIdSystem - * @requires module:modules/userId - */ - -import {ajax} from '../src/ajax.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {submodule} from '../src/hook.js'; -import { isFn, isStr, isPlainObject, logError } from '../src/utils.js'; - -const MODULE_NAME = 'haloId'; -const AU_GVLID = 561; - -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: 'halo'}); - -/** - * Param or default. - * @param {String} param - * @param {String} defaultVal - */ -function paramOrDefault(param, defaultVal, arg) { - if (isFn(param)) { - return param(arg); - } else if (isStr(param)) { - return param; - } - return defaultVal; -} - -/** @type {Submodule} */ -export const haloIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - /** - * decode the stored id value for passing to bid requests - * @function - * @param {{value:string}} value - * @returns {{haloId:Object}} - */ - decode(value) { - let haloId = storage.getDataFromLocalStorage('auHaloId'); - if (isStr(haloId)) { - return {haloId: haloId}; - } - return (value && typeof value['haloId'] === 'string') ? { 'haloId': value['haloId'] } : undefined; - }, - /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [config] - * @returns {IdResponse|undefined} - */ - getId(config) { - if (!isPlainObject(config.params)) { - config.params = {}; - } - const url = paramOrDefault(config.params.url, - `https://id.halo.ad.gt/api/v1/pbhid`, - config.params.urlArg); - - const resp = function (callback) { - let haloId = storage.getDataFromLocalStorage('auHaloId'); - if (isStr(haloId)) { - const responseObj = {haloId: haloId}; - callback(responseObj); - } else { - const callbacks = { - success: response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - } catch (error) { - logError(error); - } - } - callback(responseObj); - }, - error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); - callback(); - } - }; - ajax(url, callbacks, undefined, {method: 'GET'}); - } - }; - return {callback: resp}; - } -}; - -submodule('userId', haloIdSubmodule); diff --git a/modules/haloIdSystem.md b/modules/haloIdSystem.md deleted file mode 100644 index 7c58aea3ec6..00000000000 --- a/modules/haloIdSystem.md +++ /dev/null @@ -1,4 +0,0 @@ -## Audigent Halo has been rebranded as Hadron -## Use the Hadron Id Submodule -## The Halo modules will be removed from Prebid 7 -## Contact prebid@audigent.com for more info. diff --git a/modules/haloRtdProvider.js b/modules/haloRtdProvider.js deleted file mode 100644 index 1810bfb6f63..00000000000 --- a/modules/haloRtdProvider.js +++ /dev/null @@ -1,254 +0,0 @@ -/** - * This module adds the Audigent Halo provider to the real time data module - * The {@link module:modules/realTimeData} module is required - * The module will fetch real-time data from Audigent - * @module modules/haloRtdProvider - * @requires module:modules/realTimeData - */ -import {ajax} from '../src/ajax.js'; -import {config} from '../src/config.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {submodule} from '../src/hook.js'; -import {isFn, isStr, isArray, deepEqual, isPlainObject, logError} from '../src/utils.js'; - -const MODULE_NAME = 'realTimeData'; -const SUBMODULE_NAME = 'halo'; -const AU_GVLID = 561; - -export const HALOID_LOCAL_NAME = 'auHaloId'; -export const RTD_LOCAL_NAME = 'auHaloRtd'; -export const storage = getStorageManager({gvlid: AU_GVLID, moduleName: SUBMODULE_NAME}); - -/** - * Deep set an object unless value present. - * @param {Object} obj - * @param {String} path - * @param {Object} val - */ -function set(obj, path, val) { - const keys = path.split('.'); - const lastKey = keys.pop(); - const lastObj = keys.reduce((obj, key) => obj[key] = obj[key] || {}, obj); - lastObj[lastKey] = lastObj[lastKey] || val; -} - -/** - * Deep object merging with array deduplication. - * @param {Object} target - * @param {Object} sources - */ -function mergeDeep(target, ...sources) { - if (!sources.length) return target; - const source = sources.shift(); - - if (isPlainObject(target) && isPlainObject(source)) { - for (const key in source) { - if (isPlainObject(source[key])) { - if (!target[key]) Object.assign(target, { [key]: {} }); - mergeDeep(target[key], source[key]); - } else if (isArray(source[key])) { - if (!target[key]) { - Object.assign(target, { [key]: source[key] }); - } else if (isArray(target[key])) { - source[key].forEach(obj => { - let e = 1; - for (let i = 0; i < target[key].length; i++) { - if (deepEqual(target[key][i], obj)) { - e = 0; - break; - } - } - if (e) { - target[key].push(obj); - } - }); - } - } else { - Object.assign(target, { [key]: source[key] }); - } - } - } - - return mergeDeep(target, ...sources); -} - -/** - * Lazy merge objects. - * @param {Object} target - * @param {Object} source - */ -function mergeLazy(target, source) { - if (!isPlainObject(target)) { - target = {}; - } - - if (!isPlainObject(source)) { - source = {}; - } - - return mergeDeep(target, source); -} - -/** - * Param or default. - * @param {String} param - * @param {String} defaultVal - */ -function paramOrDefault(param, defaultVal, arg) { - if (isFn(param)) { - return param(arg); - } else if (isStr(param)) { - return param; - } - return defaultVal; -} - -/** - * Add real-time data & merge segments. - * @param {Object} bidConfig - * @param {Object} rtd - * @param {Object} rtdConfig - */ -export function addRealTimeData(bidConfig, rtd, rtdConfig) { - if (rtdConfig.params && rtdConfig.params.handleRtd) { - rtdConfig.params.handleRtd(bidConfig, rtd, rtdConfig, config); - } else { - if (isPlainObject(rtd.ortb2)) { - let ortb2 = config.getConfig('ortb2') || {}; - config.setConfig({ortb2: mergeLazy(ortb2, rtd.ortb2)}); - } - - if (isPlainObject(rtd.ortb2b)) { - let bidderConfig = config.getBidderConfig(); - - Object.keys(rtd.ortb2b).forEach(bidder => { - let rtdOptions = rtd.ortb2b[bidder] || {}; - - let bidderOptions = {}; - if (isPlainObject(bidderConfig[bidder])) { - bidderOptions = bidderConfig[bidder]; - } - - config.setBidderConfig({ - bidders: [bidder], - config: mergeLazy(bidderOptions, rtdOptions) - }); - }); - } - } -} - -/** - * Real-time data retrieval from Audigent - * @param {Object} reqBidsConfigObj - * @param {function} onDone - * @param {Object} rtdConfig - * @param {Object} userConsent - */ -export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { - if (rtdConfig && isPlainObject(rtdConfig.params) && rtdConfig.params.segmentCache) { - let jsonData = storage.getDataFromLocalStorage(RTD_LOCAL_NAME); - - if (jsonData) { - let data = JSON.parse(jsonData); - - if (data.rtd) { - addRealTimeData(bidConfig, data.rtd, rtdConfig); - onDone(); - return; - } - } - } - - const userIds = (getGlobal()).getUserIds(); - - let haloId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); - if (isStr(haloId)) { - (getGlobal()).refreshUserIds({submoduleNames: 'haloId'}); - userIds.haloId = haloId; - getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); - } else { - var script = document.createElement('script'); - script.type = 'text/javascript'; - - window.pubHaloCb = (haloId) => { - userIds.haloId = haloId; - getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); - } - - const haloIdUrl = rtdConfig.params && rtdConfig.params.haloIdUrl; - script.src = paramOrDefault(haloIdUrl, 'https://id.halo.ad.gt/api/v1/haloid', userIds); - document.getElementsByTagName('head')[0].appendChild(script); - } -} - -/** - * Async rtd retrieval from Audigent - * @param {function} onDone - * @param {Object} rtdConfig - * @param {Object} userConsent - * @param {Object} userIds - */ -export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds) { - let reqParams = {}; - - if (isPlainObject(rtdConfig)) { - set(rtdConfig, 'params.requestParams.ortb2', config.getConfig('ortb2')); - reqParams = rtdConfig.params.requestParams; - } - - if (isPlainObject(window.pubHaloPm)) { - reqParams.pubHaloPm = window.pubHaloPm; - } - - const url = `https://seg.halo.ad.gt/api/v1/rtd`; - ajax(url, { - success: function (response, req) { - if (req.status === 200) { - try { - const data = JSON.parse(response); - if (data && data.rtd) { - addRealTimeData(bidConfig, data.rtd, rtdConfig); - onDone(); - storage.setDataInLocalStorage(RTD_LOCAL_NAME, JSON.stringify(data)); - } else { - onDone(); - } - } catch (err) { - logError('unable to parse audigent segment data'); - onDone(); - } - } else if (req.status === 204) { - // unrecognized partner config - onDone(); - } - }, - error: function () { - onDone(); - logError('unable to get audigent segment data'); - } - }, - JSON.stringify({'userIds': userIds, 'config': reqParams}), - {contentType: 'application/json'} - ); -} - -/** - * Module init - * @param {Object} provider - * @param {Objkect} userConsent - * @return {boolean} - */ -function init(provider, userConsent) { - return true; -} - -/** @type {RtdSubmodule} */ -export const haloSubmodule = { - name: SUBMODULE_NAME, - getBidRequestData: getRealTimeData, - init: init -}; - -submodule(MODULE_NAME, haloSubmodule); diff --git a/modules/haloRtdProvider.md b/modules/haloRtdProvider.md deleted file mode 100644 index 6ae5a3f75fa..00000000000 --- a/modules/haloRtdProvider.md +++ /dev/null @@ -1,3 +0,0 @@ -## Audigent Halo has been rebranded as Hadron -## Use the Hadron Rtd Submodule -## The Halo modules will be removed from Prebid 7 \ No newline at end of file diff --git a/modules/haxmediaBidAdapter.md b/modules/haxmediaBidAdapter.md deleted file mode 100644 index f661a9e4e71..00000000000 --- a/modules/haxmediaBidAdapter.md +++ /dev/null @@ -1,72 +0,0 @@ -# Overview - -``` -Module Name: haxmedia Bidder Adapter -Module Type: haxmedia Bidder Adapter -Maintainer: haxmixqk@haxmediapartners.io -``` - -# Description - -Module that connects to haxmedia demand sources - -# Test Parameters -``` - var adUnits = [ - { - code:'1', - mediaTypes:{ - banner: { - sizes: [[300, 250]], - } - }, - bids:[ - { - bidder: 'haxmedia', - params: { - placementId: 0 - } - } - ] - }, - { - code:'1', - mediaTypes:{ - video: { - playerSize: [640, 480], - context: 'instream' - } - }, - bids:[ - { - bidder: 'haxmedia', - params: { - placementId: 0 - } - } - ] - }, - { - code:'1', - mediaTypes:{ - native: { - title: { - required: true - }, - icon: { - required: true, - size: [64, 64] - } - } - }, - bids:[ - { - bidder: 'haxmedia', - params: { - placementId: 0 - } - } - ] - } - ]; -``` diff --git a/modules/hpmdnetworkBidAdapter.md b/modules/hpmdnetworkBidAdapter.md deleted file mode 100644 index b7ac51a9311..00000000000 --- a/modules/hpmdnetworkBidAdapter.md +++ /dev/null @@ -1,32 +0,0 @@ -# Overview - -Module Name: HPMD Network Bidder Adapter - -Module Type: Bidder Adapter - -Maintainer: a.fominov@hpmdnetwork.ru - -# Description - -You can use this adapter to get a bid from HPMD Network. - -About us : https://www.hpmdnetwork.ru/ - - -# Test Parameters -```javascript - var adUnits = [ - { - code: 'test-div', - bids: [ - { - bidder: "hpmdnetwork", - params: { - placementId: "123" - } - } - ] - } - ]; -``` - diff --git a/modules/huddledmassesBidAdapter.md b/modules/huddledmassesBidAdapter.md deleted file mode 100644 index c743f4a2fd8..00000000000 --- a/modules/huddledmassesBidAdapter.md +++ /dev/null @@ -1,26 +0,0 @@ -# Overview - -``` -Module Name: HuddledMasses Bidder Adapter -Module Type: Bidder Adapter -Maintainer: supply@huddledmasses.com -``` - -# Description - -Module that connects to HuddledMasses' demand sources - -# Test Parameters -``` - var adUnits = [{ - code: 'placementid_0', - sizes: [[300, 250]], - bids: [{ - bidder: 'huddledmasses', - params: { - placement_id: 0 - } - }] - } - ]; -``` diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js index 98fecf04d8d..6cedb094444 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -204,7 +204,8 @@ export const spec = { */ buildRequests(validBidRequests, bidderRequest) { const payload = { - url: bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + url: bidderRequest.refererInfo.page, cmp: !!bidderRequest.gdprConsent, trafficType: TRAFFIC_TYPE_WEB, bidRequests: buildBidRequests(validBidRequests) diff --git a/modules/id5AnalyticsAdapter.js b/modules/id5AnalyticsAdapter.js index 69e303b520a..d35cdf29fca 100644 --- a/modules/id5AnalyticsAdapter.js +++ b/modules/id5AnalyticsAdapter.js @@ -1,4 +1,4 @@ -import buildAdapter from '../src/AnalyticsAdapter.js'; +import buildAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; @@ -141,7 +141,7 @@ const ENABLE_FUNCTION = (config) => { logInfo('id5Analytics: Tracking events', _this.eventsToTrack); if (sampling > 0 && _this.random() < (1 / sampling)) { // Init the module only if we got lucky - logInfo('id5Analytics: Selected by sampling. Starting up!') + logInfo('id5Analytics: Selected by sampling. Starting up!'); // Clean start _this.eventBuffer = {}; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index b57be00d3ac..c2c4a97c62e 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -7,19 +7,19 @@ import { deepAccess, - logInfo, deepSetValue, - logError, isEmpty, isEmptyStr, + logError, + logInfo, logWarn, safeJSONParse } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { submodule } from '../src/hook.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { uspDataHandler } from '../src/adapterManager.js'; +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {uspDataHandler} from '../src/adapterManager.js'; const MODULE_NAME = 'id5Id'; const GVLID = 131; @@ -28,10 +28,11 @@ export const ID5_STORAGE_NAME = 'id5id'; export const ID5_PRIVACY_STORAGE_NAME = `${ID5_STORAGE_NAME}_privacy`; const LOCAL_STORAGE = 'html5'; const LOG_PREFIX = 'User ID - ID5 submodule: '; +const ID5_API_CONFIG_URL = 'https://id5-sync.com/api/config/prebid'; // order the legacy cookie names in reverse priority order so the last // cookie in the array is the most preferred to use -const LEGACY_COOKIE_NAMES = [ 'pbjs-id5id', 'id5id.1st', 'id5id' ]; +const LEGACY_COOKIE_NAMES = ['pbjs-id5id', 'id5id.1st', 'id5id']; export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); @@ -102,92 +103,27 @@ export const id5IdSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function getId - * @param {SubmoduleConfig} config + * @param {SubmoduleConfig} submoduleConfig * @param {ConsentData} consentData * @param {(Object|undefined)} cacheIdObj * @returns {IdResponse|undefined} */ - getId(config, consentData, cacheIdObj) { - if (!hasRequiredConfig(config)) { + getId(submoduleConfig, consentData, cacheIdObj) { + if (!hasRequiredConfig(submoduleConfig)) { return undefined; } - const url = `https://id5-sync.com/g/v2/${config.params.partner}.json`; - const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; - const usp = uspDataHandler.getConsentData(); - const referer = getRefererInfo(); - const signature = (cacheIdObj && cacheIdObj.signature) ? cacheIdObj.signature : getLegacyCookieSignature(); - const data = { - 'partner': config.params.partner, - 'gdpr': hasGdpr, - 'nbPage': incrementNb(config.params.partner), - 'o': 'pbjs', - 'rf': referer.referer, - 'top': referer.reachedTop ? 1 : 0, - 'u': referer.stack[0] || window.location.href, - 'v': '$prebid.version$' - }; - - // pass in optional data, but only if populated - if (hasGdpr && typeof consentData.consentString !== 'undefined' && !isEmpty(consentData.consentString) && !isEmptyStr(consentData.consentString)) { - data.gdpr_consent = consentData.consentString; - } - if (typeof usp !== 'undefined' && !isEmpty(usp) && !isEmptyStr(usp)) { - data.us_privacy = usp; - } - if (typeof signature !== 'undefined' && !isEmptyStr(signature)) { - data.s = signature; - } - if (typeof config.params.pd !== 'undefined' && !isEmptyStr(config.params.pd)) { - data.pd = config.params.pd; - } - if (typeof config.params.provider !== 'undefined' && !isEmptyStr(config.params.provider)) { - data.provider = config.params.provider; - } - - const abTestingConfig = getAbTestingConfig(config); - if (abTestingConfig.enabled === true) { - data.ab_testing = { - enabled: true, - control_group_pct: abTestingConfig.controlGroupPct // The server validates - }; - } - - const resp = function (callback) { - const callbacks = { - success: response => { - let responseObj; - if (response) { - try { - responseObj = JSON.parse(response); - logInfo(LOG_PREFIX + 'response received from the server', responseObj); - - resetNb(config.params.partner); - - if (responseObj.privacy) { - storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(responseObj.privacy), NB_EXP_DAYS); - } - - // TODO: remove after requiring publishers to use localstorage and - // all publishers have upgraded - if (config.storage.type === LOCAL_STORAGE) { - removeLegacyCookies(config.params.partner); - } - } catch (error) { - logError(LOG_PREFIX + error); - } - } - callback(responseObj); - }, - error: error => { + const resp = function (cbFunction) { + new IdFetchFlow(submoduleConfig, consentData, cacheIdObj, uspDataHandler.getConsentData()).execute() + .then(response => { + cbFunction(response) + }) + .catch(error => { logError(LOG_PREFIX + 'getId fetch encountered an error', error); - callback(); - } - }; - logInfo(LOG_PREFIX + 'requesting an ID from the server', data); - ajax(url, callbacks, JSON.stringify(data), { method: 'POST', withCredentials: true }); + cbFunction(); + }); }; - return { callback: resp }; + return {callback: resp}; }, /** @@ -212,6 +148,139 @@ export const id5IdSubmodule = { } }; +class IdFetchFlow { + constructor(submoduleConfig, gdprConsentData, cacheIdObj, usPrivacyData) { + this.submoduleConfig = submoduleConfig + this.gdprConsentData = gdprConsentData + this.cacheIdObj = cacheIdObj + this.usPrivacyData = usPrivacyData + } + + execute() { + return this.#callForConfig(this.submoduleConfig) + .then(fetchFlowConfig => { + return this.#callForExtensions(fetchFlowConfig.extensionsCall) + .then(extensionsData => { + return this.#callId5Fetch(fetchFlowConfig.fetchCall, extensionsData) + }) + }) + .then(fetchCallResponse => { + try { + resetNb(this.submoduleConfig.params.partner); + if (fetchCallResponse.privacy) { + storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(fetchCallResponse.privacy), NB_EXP_DAYS); + } + } catch (error) { + logError(LOG_PREFIX + error); + } + return fetchCallResponse; + }) + } + + #ajaxPromise(url, data, options) { + return new Promise((resolve, reject) => { + ajax(url, + { + success: function (res) { + resolve(res) + }, + error: function (err) { + reject(err) + } + }, data, options) + }) + } + + // eslint-disable-next-line no-dupe-class-members + #callForConfig(submoduleConfig) { + let url = submoduleConfig.params.configUrl || ID5_API_CONFIG_URL; // override for debug/test purposes only + return this.#ajaxPromise(url, JSON.stringify(submoduleConfig), {method: 'POST'}) + .then(response => { + let responseObj = JSON.parse(response); + logInfo(LOG_PREFIX + 'config response received from the server', responseObj); + return responseObj; + }); + } + + // eslint-disable-next-line no-dupe-class-members + #callForExtensions(extensionsCallConfig) { + if (extensionsCallConfig === undefined) { + return Promise.resolve(undefined) + } + let extensionsUrl = extensionsCallConfig.url + let method = extensionsCallConfig.method || 'GET' + let data = method === 'GET' ? undefined : JSON.stringify(extensionsCallConfig.body || {}) + return this.#ajaxPromise(extensionsUrl, data, {'method': method}) + .then(response => { + let responseObj = JSON.parse(response); + logInfo(LOG_PREFIX + 'extensions response received from the server', responseObj); + return responseObj; + }) + } + + // eslint-disable-next-line no-dupe-class-members + #callId5Fetch(fetchCallConfig, extensionsData) { + let url = fetchCallConfig.url; + let additionalData = fetchCallConfig.overrides || {}; + let data = { + ...this.#createFetchRequestData(), + ...additionalData, + extensions: extensionsData + }; + return this.#ajaxPromise(url, JSON.stringify(data), {method: 'POST', withCredentials: true}) + .then(response => { + let responseObj = JSON.parse(response); + logInfo(LOG_PREFIX + 'fetch response received from the server', responseObj); + return responseObj; + }); + } + + // eslint-disable-next-line no-dupe-class-members + #createFetchRequestData() { + const params = this.submoduleConfig.params; + const hasGdpr = (this.gdprConsentData && typeof this.gdprConsentData.gdprApplies === 'boolean' && this.gdprConsentData.gdprApplies) ? 1 : 0; + const referer = getRefererInfo(); + const signature = (this.cacheIdObj && this.cacheIdObj.signature) ? this.cacheIdObj.signature : getLegacyCookieSignature(); + const nbPage = incrementNb(params.partner); + const data = { + 'partner': params.partner, + 'gdpr': hasGdpr, + 'nbPage': nbPage, + 'o': 'pbjs', + 'rf': referer.topmostLocation, + 'top': referer.reachedTop ? 1 : 0, + 'u': referer.stack[0] || window.location.href, + 'v': '$prebid.version$', + 'storage': this.submoduleConfig.storage + }; + + // pass in optional data, but only if populated + if (hasGdpr && this.gdprConsentData.consentString !== undefined && !isEmpty(this.gdprConsentData.consentString) && !isEmptyStr(this.gdprConsentData.consentString)) { + data.gdpr_consent = this.gdprConsentData.consentString; + } + if (this.usPrivacyData !== undefined && !isEmpty(this.usPrivacyData) && !isEmptyStr(this.usPrivacyData)) { + data.us_privacy = this.usPrivacyData; + } + if (signature !== undefined && !isEmptyStr(signature)) { + data.s = signature; + } + if (params.pd !== undefined && !isEmptyStr(params.pd)) { + data.pd = params.pd; + } + if (params.provider !== undefined && !isEmptyStr(params.provider)) { + data.provider = params.provider; + } + const abTestingConfig = params.abTesting || {enabled: false}; + + if (abTestingConfig.enabled) { + data.ab_testing = { + enabled: true, control_group_pct: abTestingConfig.controlGroupPct // The server validates + }; + } + return data; + } +} + function hasRequiredConfig(config) { if (!config || !config.params || !config.params.partner || typeof config.params.partner !== 'number') { logError(LOG_PREFIX + 'partner required to be defined as a number'); @@ -242,25 +311,29 @@ export function expDaysStr(expDays) { export function nbCacheName(partnerId) { return `${ID5_STORAGE_NAME}_${partnerId}_nb`; } + export function storeNbInCache(partnerId, nb) { storeInLocalStorage(nbCacheName(partnerId), nb, NB_EXP_DAYS); } + export function getNbFromCache(partnerId) { let cacheNb = getFromLocalStorage(nbCacheName(partnerId)); return (cacheNb) ? parseInt(cacheNb) : 0; } + function incrementNb(partnerId) { const nb = (getNbFromCache(partnerId) + 1); storeNbInCache(partnerId, nb); return nb; } + function resetNb(partnerId) { storeNbInCache(partnerId, 0); } function getLegacyCookieSignature() { let legacyStoredValue; - LEGACY_COOKIE_NAMES.forEach(function(cookie) { + LEGACY_COOKIE_NAMES.forEach(function (cookie) { if (storage.getCookie(cookie)) { legacyStoredValue = safeJSONParse(storage.getCookie(cookie)) || legacyStoredValue; } @@ -268,21 +341,6 @@ function getLegacyCookieSignature() { return (legacyStoredValue && legacyStoredValue.signature) || ''; } -/** - * Remove our legacy cookie values. Needed until we move all publishers - * to html5 storage in a future release - * @param {integer} partnerId - */ -function removeLegacyCookies(partnerId) { - logInfo(LOG_PREFIX + 'removing legacy cookies'); - LEGACY_COOKIE_NAMES.forEach(function(cookie) { - storage.setCookie(`${cookie}`, ' ', expDaysStr(-1)); - storage.setCookie(`${cookie}_nb`, ' ', expDaysStr(-1)); - storage.setCookie(`${cookie}_${partnerId}_nb`, ' ', expDaysStr(-1)); - storage.setCookie(`${cookie}_last`, ' ', expDaysStr(-1)); - }); -} - /** * This will make sure we check for expiration before accessing local storage * @param {string} key @@ -303,6 +361,7 @@ export function getFromLocalStorage(key) { storage.removeDataFromLocalStorage(key); return null; } + /** * Ensure that we always set an expiration in local storage since * by default it's not required @@ -315,14 +374,4 @@ export function storeInLocalStorage(key, value, expDays) { storage.setDataInLocalStorage(`${key}`, value); } -/** - * gets the existing abTesting config or generates a default config with abTesting off - * - * @param {SubmoduleConfig|undefined} config - * @returns {Object} an object which always contains at least the property "enabled" - */ -function getAbTestingConfig(config) { - return deepAccess(config, 'params.abTesting', { enabled: false }); -} - submodule('userId', id5IdSubmodule); diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 11f8ffc5609..cf90290b1d8 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -29,7 +29,8 @@ pbjs.setConfig({ abTesting: { // optional enabled: true, // false by default controlGroupPct: 0.1 // valid values are 0.0 - 1.0 (inclusive) - } + }, + disableExtensions: false // optional }, storage: { type: 'html5', // "html5" is the required storage type @@ -43,21 +44,22 @@ pbjs.setConfig({ }); ``` -| Param under userSync.userIds[] | Scope | Type | Description | Example | +| Param under userSync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | -| name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | -| params | Required | Object | Details for the ID5 ID. | | -| params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | -| params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://support.id5.io/portal/en/kb/articles/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | -| params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | -| params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | -| params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | +| name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | +| params | Required | Object | Details for the ID5 ID. | | +| params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | +| params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://support.id5.io/portal/en/kb/articles/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | +| params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | +| params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | +| params.abTesting.enabled | Optional | Boolean | Set this to `true` to turn on this feature | `true` or `false` | | params.abTesting.controlGroupPct | Optional | Number | Must be a number between `0.0` and `1.0` (inclusive) and is used to determine the percentage of requests that fall into the control group (and thus not exposing the ID5 ID). For example, a value of `0.20` will result in 20% of requests without an ID5 ID and 80% with an ID. | `0.1` | -| storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | -| storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | -| storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | -| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | -| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | +| params.disableExtensions | Optional | Boolean | Set this to `true` to force turn off extensions call. Default `false` | `true` or `false` | +| storage | Required | Object | Storage settings for how the User ID module will cache the ID5 ID locally | | +| storage.type | Required | String | This is where the results of the user ID will be stored. ID5 **requires** `"html5"`. | `"html5"` | +| storage.name | Required | String | The name of the local storage where the user ID will be stored. ID5 **requires** `"id5id"`. | `"id5id"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. ID5 recommends `90`. | `90` | +| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 8 hours between refreshes | `8*3600` | **ATTENTION:** As of Prebid.js v4.14.0, ID5 requires `storage.type` to be `"html5"` and `storage.name` to be `"id5id"`. Using other values will display a warning today, but in an upcoming release, it will prevent the ID5 module from loading. This change is to ensure the ID5 module in Prebid.js interoperates properly with the [ID5 API](https://github.com/id5io/id5-api.js) and to reduce the size of publishers' first-party cookies that are sent to their web servers. If you have any questions, please reach out to us at [prebid@id5.io](mailto:prebid@id5.io). diff --git a/modules/idWardRtdProvider.js b/modules/idWardRtdProvider.js index a130d3cc8d2..9678739672d 100644 --- a/modules/idWardRtdProvider.js +++ b/modules/idWardRtdProvider.js @@ -5,7 +5,6 @@ * @module modules/idWardRtdProvider * @requires module:modules/realTimeData */ -import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logError} from '../src/utils.js'; @@ -15,15 +14,15 @@ const SUBMODULE_NAME = 'idWard'; export const storage = getStorageManager({moduleName: SUBMODULE_NAME}); /** - * Add real-time data & merge segments. - * @param {Object} rtd - */ -function addRealTimeData(rtd) { + * Add real-time data & merge segments. + * @param ortb2 object to merge into + * @param {Object} rtd + */ +function addRealTimeData(ortb2, rtd) { if (isPlainObject(rtd.ortb2)) { - const ortb2 = config.getConfig('ortb2') || {}; logMessage('idWardRtdProvider: merging original: ', ortb2); logMessage('idWardRtdProvider: merging in: ', rtd.ortb2); - config.setConfig({ortb2: mergeDeep(ortb2, rtd.ortb2)}); + mergeDeep(ortb2, rtd.ortb2); } } @@ -78,7 +77,7 @@ export function getRealTimeData(reqBidsConfigObj, onDone, rtdConfig, userConsent } } }; - addRealTimeData(data.rtd); + addRealTimeData(reqBidsConfigObj.ortb2Fragments?.global, data.rtd); onDone(); } } diff --git a/modules/idxBidAdapter.js b/modules/idxBidAdapter.js new file mode 100644 index 00000000000..48739275788 --- /dev/null +++ b/modules/idxBidAdapter.js @@ -0,0 +1,80 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { BANNER } from '../src/mediaTypes.js' +import { isArray, isNumber } from '../src/utils.js' + +const BIDDER_CODE = 'idx' +const ENDPOINT_URL = 'https://dev-event.dxmdp.com/rest/api/v1/bid' +const SUPPORTED_MEDIA_TYPES = [ BANNER ] + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + isBidRequestValid: function (bid) { + return isArray(bid.mediaTypes?.banner?.sizes) && bid.mediaTypes.banner.sizes.every(size => { + return isArray(size) && size.length === 2 && isNumber(size[0]) && isNumber(size[1]) + }) + }, + buildRequests: function (bidRequests, bidderRequest) { + const payload = { + id: bidderRequest.bidderRequestId, + imp: bidRequests.map(request => { + const { bidId, sizes } = request + + const item = { + id: bidId, + } + + if (request.mediaTypes.banner) { + item.banner = { + format: (request.mediaTypes.banner.sizes || sizes).map(size => { + return { w: size[0], h: size[1] } + }), + } + } + + return item + }), + } + + const payloadString = JSON.stringify(payload) + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString, + bidderRequest, + options: { + withCredentials: false, + contentType: 'application/json' + } + } + }, + interpretResponse: function (serverResponse) { + const response = serverResponse.body + + const bids = [] + + response.seatbid.forEach(seat => { + seat.bid.forEach(bid => { + bids.push({ + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + ad: bid.adm, + currency: 'USD', + netRevenue: true, + ttl: 300, + meta: { + advertiserDomains: bid.adomain || [], + }, + }) + }) + }) + + return bids + }, +} + +registerBidder(spec) diff --git a/modules/idxBidAdapter.md b/modules/idxBidAdapter.md new file mode 100644 index 00000000000..4f0e35f2c24 --- /dev/null +++ b/modules/idxBidAdapter.md @@ -0,0 +1,28 @@ +# Overview + +``` +Module Name: IDX Bidder Adapter +Module Type: Bidder Adapter +Maintainer: dmitry@brainway.co.il +``` + +# Description + +Module that connects to the IDX solution. +The IDX bidder need one mediaTypes parameter: banner + +# Test Parameters +``` + var adUnits = [{ + code: 'your-slot-div-id', // This is your slot div id + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'idx', + params: {} + }] + }] +``` diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js index 6c582df3df3..bc01896d062 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -37,36 +37,40 @@ function setImDataInCookie(value) { ); } +/** + * @param {Object} segments + * @param {Object} moduleConfig + */ +function getSegments(segments, moduleConfig) { + if (!segments) return; + const maxSegments = !Number.isNaN(moduleConfig.params.maxSegments) ? moduleConfig.params.maxSegments : 200; + return segments.slice(0, maxSegments); +} + /** * @param {string} bidderName */ export function getBidderFunction(bidderName) { const biddersFunction = { - ix: function (bid, data) { - if (data.im_segments && data.im_segments.length) { - config.setConfig({ - ix: {firstPartyData: {im_segments: data.im_segments}}, - }); - } - return bid - }, - pubmatic: function (bid, data) { + pubmatic: function (bid, data, moduleConfig) { if (data.im_segments && data.im_segments.length) { + const segments = getSegments(data.im_segments, moduleConfig); const dctr = deepAccess(bid, 'params.dctr'); deepSetValue( bid, 'params.dctr', - `${dctr ? dctr + '|' : ''}im_segments=${data.im_segments.join(',')}` + `${dctr ? dctr + '|' : ''}im_segments=${segments.join(',')}` ); } return bid }, - fluct: function (bid, data) { + fluct: function (bid, data, moduleConfig) { if (data.im_segments && data.im_segments.length) { + const segments = getSegments(data.im_segments, moduleConfig); deepSetValue( bid, 'params.kv.imsids', - data.im_segments + segments ); } return bid @@ -96,15 +100,15 @@ export function setRealTimeData(bidConfig, moduleConfig, data) { const utils = {deepSetValue, deepAccess, logInfo, logError, mergeDeep}; if (data.im_segments) { - const ortb2 = config.getConfig('ortb2') || {}; - deepSetValue(ortb2, 'user.ext.data.im_segments', data.im_segments); - config.setConfig({ortb2: ortb2}); + const segments = getSegments(data.im_segments, moduleConfig); + const ortb2 = bidConfig.ortb2Fragments?.global || {}; + deepSetValue(ortb2, 'user.ext.data.im_segments', segments); if (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues')) { window.googletag = window.googletag || {cmd: []}; window.googletag.cmd = window.googletag.cmd || []; window.googletag.cmd.push(() => { - window.googletag.pubads().setTargeting('im_segments', data.im_segments); + window.googletag.pubads().setTargeting('im_segments', segments); }); } } @@ -116,7 +120,7 @@ export function setRealTimeData(bidConfig, moduleConfig, data) { if (overwriteFunction) { overwriteFunction(bid, data, utils, config); } else if (bidderFunction) { - bidderFunction(bid, data); + bidderFunction(bid, data, moduleConfig); } }) }); diff --git a/modules/imRtdProvider.md b/modules/imRtdProvider.md index 7ece2b996b4..8f31d3eb545 100644 --- a/modules/imRtdProvider.md +++ b/modules/imRtdProvider.md @@ -21,7 +21,8 @@ pbjs.setConfig( waitForIt: true, params: { cid: 5126, // Set your Intimate Merger Customer ID here for production - setGptKeyValues: true + setGptKeyValues: true, + maxSegments: 200 // maximum number is 200 } } ] @@ -39,3 +40,4 @@ pbjs.setConfig( | params | Required | Object | Details of module params. | | | params.cid | Required | Number | This is the Customer ID value obtained via Intimate Merger. | `5126` | | params.setGptKeyValues | Optional | Boolean | This is set targeting for GPT/GAM. Default setting is true. | `true` | +| params.maxSegments | Optional | Number | This is set maximum number of rtd segments at once. Default setting is 200. | `200` | diff --git a/modules/imonomyBidAdapter.md b/modules/imonomyBidAdapter.md deleted file mode 100644 index 451eb0994d8..00000000000 --- a/modules/imonomyBidAdapter.md +++ /dev/null @@ -1,29 +0,0 @@ -# Overview - -**Module Name**: Imonomy Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: support@imonomy.com - -# Description - -Connects to Imonomy demand source to fetch bids. - -# Test Parameters -``` - var adUnits = [{ - code: 'banner-ad-div', - sizes: [[300, 250]], - - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'imonomy', - params: { - placementId: 'e69148e0ba6c4c07977dc2daae5e1577', - hbid: '14567718624', - floorPrice: 0.5 - } - }] - }]; -``` - - diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index b204e81f22c..0717cf43741 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -25,7 +25,7 @@ const getDeviceType = () => { return 4; } return 2; -} +}; const createOpenRtbRequest = (validBidRequests, bidderRequest) => { // Create request and set imp bids inside @@ -37,9 +37,13 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { source: {tid: bidderRequest.auctionId} }; + // Get the url parameters + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const checkPrebid = urlParams.get('_checkPrebid'); // Force impactify debugging parameter - if (window.localStorage.getItem('_im_db_bidder') != null) { - request.test = Number(window.localStorage.getItem('_im_db_bidder')); + if (checkPrebid != null) { + request.test = Number(checkPrebid); } // Set Schain in request @@ -65,7 +69,7 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, language: ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en', }; - request.site = {page: bidderRequest.refererInfo.referer}; + request.site = {page: bidderRequest.refererInfo.page}; // Handle privacy settings for GDPR/CCPA/COPPA let gdprApplies = 0; diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index a0453466b87..c8fc8eb7a2a 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,12 +1,25 @@ import { - cleanObj, deepAccess, deepClone, deepSetValue, getBidIdParameter, getBidRequest, getDNT, - getUniqueIdentifierStr, isFn, isPlainObject, logWarn, mergeDeep, parseUrl + cleanObj, + deepAccess, + deepClone, + deepSetValue, + getBidIdParameter, + getBidRequest, + getDNT, + getUniqueIdentifierStr, + isFn, + isPlainObject, + logWarn, + mergeDeep } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {createEidsArray} from './userId/eids.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {loadExternalScript} from '../src/adloader.js'; const BIDDER_CODE = 'improvedigital'; const CREATIVE_TTL = 300; @@ -80,6 +93,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests(bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + const request = { cur: [config.getConfig('currency.adServerCurrency') || 'USD'], ext: { @@ -108,7 +124,7 @@ export const spec = { if (bidderRequest) { // GDPR - const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent'); if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { deepSetValue(request, 'regs.ext.gdpr', Number(gdprConsent.gdprApplies)); @@ -197,7 +213,7 @@ export const spec = { ID_RESPONSE.buildAd(bid, bidRequest, bidObject); - ID_RAZR.addBidData({ + ID_RAZR.forwardBid({ bidRequest, bid }); @@ -217,7 +233,7 @@ export const spec = { * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { - if (config.getConfig('coppa') === true || !ID_UTIL.hasPurpose1Consent(gdprConsent)) { + if (config.getConfig('coppa') === true || !hasPurpose1Consent(gdprConsent)) { return []; } @@ -419,6 +435,9 @@ const ID_REQUEST = { return null; } const request = { + eventtrackers: [ + {event: 1, methods: [1, 2]} + ], assets: [], } for (let i of Object.keys(nativeParams)) { @@ -480,20 +499,20 @@ const ID_REQUEST = { buildSiteOrApp(request, bidderRequest) { const app = {}; const configAppSettings = config.getConfig('app') || {}; - const fpdAppSettings = config.getConfig('ortb2.app') || {}; + const fpdAppSettings = bidderRequest.ortb2?.app || {}; mergeDeep(app, configAppSettings, fpdAppSettings); if (Object.keys(app).length !== 0) { request.app = app; } else { const site = {}; - const url = config.getConfig('pageUrl') || deepAccess(bidderRequest, 'refererInfo.referer'); + const url = deepAccess(bidderRequest, 'refererInfo.page'); if (url) { site.page = url; - site.domain = parseUrl(url).hostname; + site.domain = bidderRequest.refererInfo.domain; } const configSiteSettings = config.getConfig('site') || {}; - const fpdSiteSettings = config.getConfig('ortb2.site') || {}; + const fpdSiteSettings = deepAccess(bidderRequest, 'ortb2.site') || {}; mergeDeep(site, configSiteSettings, fpdSiteSettings); request.site = site; } @@ -622,46 +641,58 @@ const ID_OUTSTREAM = { }; const ID_RAZR = { - RENDERER_URL: 'https://razr.improvedigital.com/renderer.js', - addBidData({bid, bidRequest}) { - if (this.isValidBid(bid)) { - bid.renderer = Renderer.install({ - url: this.RENDERER_URL, - config: {bidRequest} - }); - bid.renderer.setRender(this.render); - } - }, + RENDERER_URL: 'https://cdn.360yield.com/razr/tag.js', - isValidBid(bid) { - return bid && /razr:\/\//.test(bid.ad); - }, + forwardBid({bidRequest, bid}) { + if (bid.mediaType !== BANNER) { + return; + } - render(bid) { - const {bidRequest} = bid.renderer.getConfig(); - - const payload = { - type: 'prebid', - bidRequest, - bid, - config: mergeDeep( - {}, - config.getConfig('improvedigital.rendererConfig'), - deepAccess(bidRequest, 'params.rendererConfig') - ) + const cfg = { + prebid: { + bidRequest, + bid + } }; - const razr = window.razr = window.razr || {}; - razr.queue = razr.queue || []; - razr.queue.push(payload); - } -}; + const cfgStr = JSON.stringify(cfg).replace(/<\/script>/g, '\\x3C/script>'); + const s = ``; + bid.ad = bid.ad.replace(/]*>/, match => match + s); + + this.installListener(); + }, -const ID_UTIL = { - hasPurpose1Consent(gdprConsent) { - if (gdprConsent && gdprConsent.gdprApplies && gdprConsent.apiVersion === 2) { - return (deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true); + installListener() { + if (this._listenerInstalled) { + return; } - return true; + + window.addEventListener('message', function(e) { + const data = e.data?.razr?.load; + if (!data) { + return; + } + + if (e.source) { + data.source = e.source; + if (data.id) { + e.source.postMessage({ + razr: { + id: data.id + } + }, '*'); + } + } + + const ns = window.razr = window.razr || {}; + ns.q = ns.q || []; + ns.q.push(data); + + if (!ns.loaded) { + loadExternalScript(ID_RAZR.RENDERER_URL, BIDDER_CODE); + } + }); + + this._listenerInstalled = true; } }; diff --git a/modules/imuIdSystem.js b/modules/imuIdSystem.js index 72e81d243a3..41ff95b6702 100644 --- a/modules/imuIdSystem.js +++ b/modules/imuIdSystem.js @@ -13,21 +13,12 @@ import { getStorageManager } from '../src/storageManager.js'; export const storage = getStorageManager(); export const storageKey = '__im_uid'; +export const storagePpKey = '__im_ppid'; export const cookieKey = '_im_vid'; -export const apiUrl = 'https://audiencedata.im-apps.net/imuid/get'; +export const apiDomain = 'sync6.im-apps.net'; const storageMaxAge = 1800000; // 30 minites (30 * 60 * 1000) const cookiesMaxAge = 97200000000; // 37 months ((365 * 3 + 30) * 24 * 60 * 60 * 1000) -export function setImDataInLocalStorage(value) { - storage.setDataInLocalStorage(storageKey, value); - storage.setDataInLocalStorage(`${storageKey}_mt`, new Date(timestamp()).toUTCString()); -} - -export function removeImDataFromLocalStorage() { - storage.removeDataFromLocalStorage(storageKey); - storage.removeDataFromLocalStorage(`${storageKey}_mt`); -} - function setImDataInCookie(value) { storage.setCookie( cookieKey, @@ -37,6 +28,12 @@ function setImDataInCookie(value) { ); } +export function removeImDataFromLocalStorage() { + storage.removeDataFromLocalStorage(storageKey); + storage.removeDataFromLocalStorage(`${storageKey}_mt`); + storage.removeDataFromLocalStorage(storagePpKey); +} + export function getLocalData() { const mt = storage.getDataFromLocalStorage(`${storageKey}_mt`); let expired = true; @@ -45,17 +42,27 @@ export function getLocalData() { } return { id: storage.getDataFromLocalStorage(storageKey), + ppid: storage.getDataFromLocalStorage(storagePpKey), vid: storage.getCookie(cookieKey), expired: expired }; } +export function getApiUrl(cid, url) { + if (url) { + return `${url}?cid=${cid}`; + } + return `https://${apiDomain}/${cid}/pid`; +} + export function apiSuccessProcess(jsonResponse) { if (!jsonResponse) { return; } - if (jsonResponse.uid) { - setImDataInLocalStorage(jsonResponse.uid); + if (jsonResponse.uid && jsonResponse.ppid) { + storage.setDataInLocalStorage(storageKey, jsonResponse.uid); + storage.setDataInLocalStorage(`${storageKey}_mt`, new Date(timestamp()).toUTCString()); + storage.setDataInLocalStorage(storagePpKey, jsonResponse.ppid); if (jsonResponse.vid) { setImDataInCookie(jsonResponse.vid); } @@ -77,7 +84,11 @@ export function getApiCallback(callback) { } } if (callback && responseObj.uid) { - callback(responseObj.uid); + const callbackObj = { + imuid: responseObj.uid, + imppid: responseObj.ppid + }; + callback(callbackObj); } }, error: error => { @@ -95,13 +106,6 @@ export function callImuidApi(apiUrl) { }; } -export function getApiUrl(cid, url) { - if (url) { - return `${url}?cid=${cid}`; - } - return `${apiUrl}?cid=${cid}`; -} - /** @type {Submodule} */ export const imuIdSubmodule = { /** @@ -112,18 +116,21 @@ export const imuIdSubmodule = { /** * decode the stored id value for passing to bid requests * @function - * @returns {{imuid: string} | undefined} + * @returns {{imuid: string, imppid: string} | undefined} */ - decode(id) { - if (id && typeof id === 'string') { - return {imuid: id}; + decode(ids) { + if (ids && typeof ids === 'object') { + return { + imuid: ids.imuid, + imppid: ids.imppid + }; } return undefined; }, /** * @function * @param {SubmoduleConfig} [config] - * @returns {{id: string} | undefined | {callback:function}}} + * @returns {{id:{imuid: string, imppid: string}} | undefined | {callback:function}}} */ getId(config) { const configParams = (config && config.params) || {}; @@ -139,12 +146,17 @@ export const imuIdSubmodule = { } if (!localData.id) { - return {callback: callImuidApi(apiUrl)} + return {callback: callImuidApi(apiUrl)}; } if (localData.expired) { callImuidApi(apiUrl)(); } - return {id: localData.id}; + return { + id: { + imuid: localData.id, + imppid: localData.ppid + } + }; } }; diff --git a/modules/imuIdSystem.md b/modules/imuIdSystem.md index 81aa87ba1d4..cda7fa6528d 100644 --- a/modules/imuIdSystem.md +++ b/modules/imuIdSystem.md @@ -16,6 +16,7 @@ The following configuration parameters are available: ```javascript pbjs.setConfig({ userSync: { + ppid: 'ppid.intimatemerger.com', // GAM Publisher Provided id support userIds: [{ name: 'imuid', params: { diff --git a/modules/incrxBidAdapter.js b/modules/incrxBidAdapter.js index 5926f5a8729..914ef0b904e 100644 --- a/modules/incrxBidAdapter.js +++ b/modules/incrxBidAdapter.js @@ -36,7 +36,8 @@ export const spec = { _vzPlacementId: bidRequest.params.placementId, sizes: sizes, _slotBidId: bidRequest.bidId, - _rqsrc: bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + _rqsrc: bidderRequest.refererInfo.page, }; const payload = { diff --git a/modules/incrxBidAdapter.md b/modules/incrxBidAdapter.md deleted file mode 100644 index 7feda2b2b6d..00000000000 --- a/modules/incrxBidAdapter.md +++ /dev/null @@ -1,31 +0,0 @@ -# Overview - -``` -Module Name: IncrementX Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid-team@vertoz.com -``` - -# Description - -Connects to IncrementX exchange for bids. -IncrementX Bidder adapter supports Banner ads. -Use bidder code ```incrementx``` for all IncrementX traffic. - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - sizes: [[300, 250], [300,600]], // a display size(s) - bids: [{ - bidder: 'incrementx', - params: { - placementId: 'PNX-HB-F796830VCF3C4B' - } - }] - }, -]; -``` - diff --git a/modules/inmarBidAdapter.js b/modules/inmarBidAdapter.js index 0e056551b35..42bd64ee816 100755 --- a/modules/inmarBidAdapter.js +++ b/modules/inmarBidAdapter.js @@ -1,4 +1,4 @@ -import { logError } from '../src/utils.js'; +import {logError, mergeDeep} from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -34,13 +34,14 @@ export const spec = { bidRequests: validBidRequests, auctionStart: bidderRequest.auctionStart, timeout: bidderRequest.timeout, - refererInfo: bidderRequest.refererInfo, + // TODO: please do not send internal data structures over the network + refererInfo: bidderRequest.refererInfo.legacy, start: bidderRequest.start, gdprConsent: bidderRequest.gdprConsent, uspConsent: bidderRequest.uspConsent, currencyCode: config.getConfig('currency.adServerCurrency'), coppa: config.getConfig('coppa'), - firstPartyData: config.getLegacyFpd(config.getConfig('ortb2')), + firstPartyData: getLegacyFpd(bidderRequest.ortb2), prebidVersion: '$prebid.version$' }; @@ -107,4 +108,25 @@ export const spec = { } }; +function getLegacyFpd(ortb2) { + if (typeof ortb2 !== 'object') return; + + let duplicate = {}; + + Object.keys(ortb2).forEach((type) => { + let prop = (type === 'site') ? 'context' : type; + duplicate[prop] = (prop === 'context' || prop === 'user') ? Object.keys(ortb2[type]).filter(key => key !== 'data').reduce((result, key) => { + if (key === 'ext') { + mergeDeep(result, ortb2[type][key]); + } else { + mergeDeep(result, {[key]: ortb2[type][key]}); + } + + return result; + }, {}) : ortb2[type]; + }); + + return duplicate; +} + registerBidder(spec); diff --git a/modules/innityBidAdapter.js b/modules/innityBidAdapter.js index 0a2f701ef64..71fe588441c 100644 --- a/modules/innityBidAdapter.js +++ b/modules/innityBidAdapter.js @@ -23,7 +23,7 @@ export const spec = { output: 'js', pub: bidRequest.params.pub, zone: bidRequest.params.zone, - url: bidderRequest && bidderRequest.refererInfo ? encodeURIComponent(bidderRequest.refererInfo.referer) : '', + url: bidderRequest && bidderRequest.refererInfo ? encodeURIComponent(bidderRequest.refererInfo.page) : '', width: arrSize[0], height: arrSize[1], vpw: window.screen.width, diff --git a/modules/inskinBidAdapter.js b/modules/inskinBidAdapter.js index 6f0023498aa..f3464765bde 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -50,7 +50,7 @@ export const spec = { placements: [], time: Date.now(), user: {}, - url: bidderRequest.refererInfo.referer, + url: bidderRequest.refererInfo.page, enableBotFiltering: true, includePricingData: true, parallel: true @@ -83,31 +83,29 @@ export const spec = { gdprConsentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true }; - if (bidderRequest.gdprConsent.apiVersion === 2) { - const purposes = [ - {id: 1, kw: 'nocookies'}, - {id: 2, kw: 'nocontext'}, - {id: 3, kw: 'nodmp'}, - {id: 4, kw: 'nodata'}, - {id: 7, kw: 'noclicks'}, - {id: 9, kw: 'noresearch'} - ]; - - const d = bidderRequest.gdprConsent.vendorData; - - if (d) { - if (d.purposeOneTreatment) { - data.keywords.push('cst-nodisclosure'); - restrictions.push('nodisclosure'); - } - - purposes.map(p => { - if (!checkConsent(p.id, d)) { - data.keywords.push('cst-' + p.kw); - restrictions.push(p.kw); - } - }); + const purposes = [ + {id: 1, kw: 'nocookies'}, + {id: 2, kw: 'nocontext'}, + {id: 3, kw: 'nodmp'}, + {id: 4, kw: 'nodata'}, + {id: 7, kw: 'noclicks'}, + {id: 9, kw: 'noresearch'} + ]; + + const d = bidderRequest.gdprConsent.vendorData; + + if (d) { + if (d.purposeOneTreatment) { + data.keywords.push('cst-nodisclosure'); + restrictions.push('nodisclosure'); } + + purposes.map(p => { + if (!checkConsent(p.id, d)) { + data.keywords.push('cst-' + p.kw); + restrictions.push(p.kw); + } + }); } } @@ -186,7 +184,7 @@ export const spec = { bid.currency = 'USD'; bid.creativeId = decision.adId; bid.ttl = 360; - bid.meta = { advertiserDomains: decision.adomain ? decision.adomain : [] } + bid.meta = { advertiserDomains: decision.adomain ? decision.adomain : [] }; bid.netRevenue = true; bidResponses.push(bid); diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 4d069cc91a6..150e9d3c5c2 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -25,7 +25,7 @@ function getUserId() { let uid; if (storage.localStorageIsEnabled()) { - uid = localStorage.getItem(USER_ID_KEY); + uid = storage.getDataFromLocalStorage(USER_ID_KEY); } else { uid = storage.getCookie(USER_ID_KEY); } @@ -39,11 +39,11 @@ function getUserId() { function setUserId(userId) { if (storage.localStorageIsEnabled()) { - localStorage.setItem(USER_ID_KEY, userId); + storage.setDataInLocalStorage(USER_ID_KEY, userId); } if (storage.cookiesAreEnabled()) { - const expires = new Date(Date.now() + USER_ID_COOKIE_EXP).toISOString(); + const expires = new Date(Date.now() + USER_ID_COOKIE_EXP).toUTCString(); storage.setCookie(USER_ID_KEY, userId, expires); } } @@ -71,8 +71,10 @@ function buildVideo(bidRequest) { const w = deepAccess(bidRequest, 'mediaTypes.video.w'); const h = deepAccess(bidRequest, 'mediaTypes.video.h'); const mimes = deepAccess(bidRequest, 'mediaTypes.video.mimes'); + const placement = deepAccess(bidRequest, 'mediaTypes.video.placement') || 3; return { + placement, mimes, w, h, @@ -177,9 +179,10 @@ function buildRequest(validBidRequests, bidderRequest) { tid: bidderRequest.auctionId, }, site: { - domain: location.hostname, - page: location.href, - ref: bidderRequest.refererInfo.referer, + // TODO: are these the right refererInfo values? + domain: bidderRequest.refererInfo.domain, + page: bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref, }, device: buildDevice(), regs: buildRegs(bidderRequest), @@ -329,6 +332,13 @@ function validateVideo(bid) { return false; } + const placement = deepAccess(bid, 'mediaTypes.video.placement'); + + if (typeof placement !== 'undefined' && typeof placement !== 'number') { + logError('insticator: video placement is not a number'); + return false; + } + return true; } diff --git a/modules/integr8BidAdapter.js b/modules/integr8BidAdapter.js index d61fe624c59..a85e9b0a55c 100644 --- a/modules/integr8BidAdapter.js +++ b/modules/integr8BidAdapter.js @@ -46,7 +46,7 @@ export const spec = { bidderRequestId = bidderRequest.bidderRequestId; if (bidderRequest.refererInfo) { - url = bidderRequest.refererInfo.referer; + url = bidderRequest.refererInfo.page; } } @@ -78,7 +78,7 @@ export const spec = { placements: placements, contents: contents, data: data - } + }; return [{ method: 'POST', @@ -117,7 +117,7 @@ export const spec = { } return bidResponses; } -} +}; /** * Generate size param for bid request using sizes array diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 1347fa04bd5..563435dee65 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -118,7 +118,7 @@ export const intentIqIdSubmodule = { logError('User ID - intentIqId submodule requires a valid partner to be defined'); return; } - if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { FIRST_PARTY_DATA_KEY += '_' + configParams.partner } + if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { FIRST_PARTY_DATA_KEY += '_' + configParams.partner; } let rrttStrtTime = 0; // Read Intent IQ 1st party id or generate it if none exists @@ -169,7 +169,7 @@ export const intentIqIdSubmodule = { shouldUpdateLs = true; } if (shouldUpdateLs === true) { - partnerData.date = Date.now() + partnerData.date = Date.now(); storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData)); } diff --git a/modules/interactiveOffersBidAdapter.js b/modules/interactiveOffersBidAdapter.js index d8a106623fd..1fad231dff2 100644 --- a/modules/interactiveOffersBidAdapter.js +++ b/modules/interactiveOffersBidAdapter.js @@ -1,7 +1,6 @@ -import { logWarn, isNumber } from '../src/utils.js'; +import {isNumber, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; const BIDDER_CODE = 'interactiveOffers'; const ENDPOINT = 'https://prebid.ioadx.com/bidRequest/?partnerId='; @@ -77,13 +76,15 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest) { payload: {}, partnerId: null }; + // TODO: these should probably look at refererInfo let pageURL = window.location.href; let domain = window.location.hostname; let secure = (window.location.protocol == 'https:' ? 1 : 0); let openRTBRequest = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequest'])); openRTBRequest.id = prebidRequest.auctionId; openRTBRequest.ext = { - refererInfo: prebidRequest.refererInfo, + // TODO: please do not send internal data structures over the network + refererInfo: prebidRequest.refererInfo.legacy, auctionId: prebidRequest.auctionId }; @@ -92,11 +93,11 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest) { openRTBRequest.site.name = domain; openRTBRequest.site.domain = domain; openRTBRequest.site.page = pageURL; - openRTBRequest.site.ref = prebidRequest.refererInfo.referer; + openRTBRequest.site.ref = prebidRequest.refererInfo.ref; openRTBRequest.site.publisher = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSitePublisher'])); openRTBRequest.site.publisher.id = 0; - openRTBRequest.site.publisher.name = config.getConfig('publisherDomain'); + openRTBRequest.site.publisher.name = prebidRequest.refererInfo.domain; openRTBRequest.site.publisher.domain = domain; openRTBRequest.site.publisher.domain = domain; @@ -168,7 +169,7 @@ function parseResponseOpenRTBToPrebidjs(openRTBResponse) { mediaType: 'banner', primaryCatId: bid.cat[0] || '', secondaryCatIds: bid.cat - } + }; prebidResponse.push(prebid); }); } diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 717a886a1f6..c3e5cf6cca8 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -9,11 +9,12 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 8, + PREBID_VERSION: 9, METHOD: 'GET', INVIBES_VENDOR_ID: 436, USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'], - META_TAXONOMY: ['networkId', 'networkName', 'agencyId', 'agencyName', 'advertiserId', 'advertiserName', 'advertiserDomains', 'brandId', 'brandName', 'primaryCatId', 'secondaryCatIds', 'mediaType'] + META_TAXONOMY: ['networkId', 'networkName', 'agencyId', 'agencyName', 'advertiserId', 'advertiserName', 'advertiserDomains', 'brandId', 'brandName', 'primaryCatId', 'secondaryCatIds', 'mediaType'], + DISABLE_USER_SYNC: true }; const storage = getStorageManager({gvlid: CONSTANTS.INVIBES_VENDOR_ID, bidderCode: CONSTANTS.BIDDER_CODE}); @@ -40,15 +41,7 @@ export const spec = { interpretResponse: function (responseObj, requestParams) { return handleResponse(responseObj, requestParams != null ? requestParams.bidRequests : null); }, - getUserSyncs: function (syncOptions) { - if (syncOptions.iframeEnabled) { - const syncUrl = buildSyncUrl(); - return { - type: 'iframe', - url: syncUrl - }; - } - } + getUserSyncs: getUserSync, }; registerBidder(spec); @@ -60,7 +53,9 @@ invibes.purposes = invibes.purposes || [false, false, false, false, false, false invibes.legitimateInterests = invibes.legitimateInterests || [false, false, false, false, false, false, false, false, false, false]; invibes.placementBids = invibes.placementBids || []; invibes.pushedCids = invibes.pushedCids || {}; +let preventPageViewEvent = false; let _customUserSync; +let _disableUserSyncs; function isBidRequestValid(bid) { if (typeof bid.params !== 'object') { @@ -75,6 +70,18 @@ function isBidRequestValid(bid) { return true; } +function getUserSync(syncOptions) { + if (syncOptions.iframeEnabled) { + if (!(_disableUserSyncs == null || _disableUserSyncs == undefined ? CONSTANTS.DISABLE_USER_SYNC : _disableUserSyncs)) { + const syncUrl = buildSyncUrl(); + return { + type: 'iframe', + url: syncUrl + }; + } + } +} + function buildRequest(bidRequests, bidderRequest) { bidderRequest = bidderRequest || {}; const _placementIds = []; @@ -89,6 +96,7 @@ function buildRequest(bidRequests, bidderRequest) { _domainId = _domainId || bidRequest.params.domainId; _customEndpoint = _customEndpoint || bidRequest.params.customEndpoint; _customUserSync = _customUserSync || bidRequest.params.customUserSync; + _disableUserSyncs = bidRequest?.params?.disableUserSyncs; _userId = _userId || bidRequest.userId; }); @@ -129,6 +137,7 @@ function buildRequest(bidRequests, bidderRequest) { tc: invibes.gdpr_consent, isLocalStorageEnabled: storage.hasLocalStorage(), + preventPageViewEvent: preventPageViewEvent, }; let lid = readFromLocalStorage('ivbsdid'); @@ -158,6 +167,8 @@ function buildRequest(bidRequests, bidderRequest) { let endpoint = createEndpoint(_customEndpoint, _domainId, _placementIds); + preventPageViewEvent = true; + return { method: CONSTANTS.METHOD, url: endpoint, @@ -381,7 +392,10 @@ function getUserIds(bidUserId) { function parseQueryStringParams() { let params = {}; try { - params = JSON.parse(localStorage.ivbs); + let storedParam = storage.getDataFromLocalStorage('ivbs'); + if (storedParam != null) { + params = JSON.parse(storedParam); + } } catch (e) { } let re = /[\\?&]([^=]+)=([^\\?&#]+)/g; diff --git a/modules/invisiblyAnalyticsAdapter.js b/modules/invisiblyAnalyticsAdapter.js index 1f0bbfd46c3..a4f4eba271c 100644 --- a/modules/invisiblyAnalyticsAdapter.js +++ b/modules/invisiblyAnalyticsAdapter.js @@ -2,7 +2,7 @@ * invisiblyAdapterAdapter.js - analytics adapter for Invisibly */ import { ajaxBuilder } from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { generateUUID, logInfo } from '../src/utils.js'; diff --git a/modules/ipromBidAdapter.js b/modules/ipromBidAdapter.js index 46582ce95a1..eaf20ad3ad3 100644 --- a/modules/ipromBidAdapter.js +++ b/modules/ipromBidAdapter.js @@ -34,7 +34,8 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const payload = { bids: validBidRequests, - referer: bidderRequest.refererInfo, + // TODO: please do not send internal data structures over the network + referer: bidderRequest.refererInfo.legacy, version: VERSION }; const payloadString = JSON.stringify(payload); diff --git a/modules/iqmBidAdapter.js b/modules/iqmBidAdapter.js index 75854d39fd5..1c36bafd3ce 100644 --- a/modules/iqmBidAdapter.js +++ b/modules/iqmBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, getBidIdParameter, isArray, _each, getWindowTop, parseUrl } from '../src/utils.js'; +import {_each, deepAccess, getBidIdParameter, isArray} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -102,7 +102,7 @@ export const spec = { imp.video = _buildVideoORTB(bid); imp.mediatype = 'video'; } - const site = getSite(bid); + const site = getSite(bidderRequest); let device = getDevice(bid.params); finalRequest = { sizes: bid.sizes, @@ -116,6 +116,8 @@ export const spec = { adUnitCode: bid.adUnitCode, bidderRequestId: bid.bidderRequestId, uuid: bid.bidId, + // TODO: please do not send internal data structures over the network + // I am not going to attempt to accommodate this, no way this is usable on their end, it changes way too frequently bidderRequest } const request = { @@ -227,19 +229,10 @@ function getSite(bidderRequest) { const {refererInfo} = bidderRequest; - if (canAccessTopWindow()) { - const wt = getWindowTop(); - domain = wt.location.hostname; - page = wt.location.href; - referrer = wt.document.referrer || ''; - } else if (refererInfo.reachedTop) { - const url = parseUrl(refererInfo.referer); - domain = url.hostname; - page = refererInfo.referer; - } else if (refererInfo.stack && refererInfo.stack.length && refererInfo.stack[0]) { - const url = parseUrl(refererInfo.stack[0]); - domain = url.hostname; - } + // TODO: are these the right refererInfo values? + domain = refererInfo.domain; + page = refererInfo.page; + referrer = refererInfo.ref; return { domain, @@ -249,20 +242,10 @@ function getSite(bidderRequest) { }; }; -function canAccessTopWindow() { - try { - if (getWindowTop().location.href) { - return true; - } - } catch (error) { - return false; - } -} - function _buildVideoORTB(bidRequest) { const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video'); const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); - const video = {} + const video = {}; const videoParams = { ...videoAdUnit, diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index 6c0a2e5f56d..f2de447d6a7 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -2,6 +2,7 @@ import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'iqzone'; const AD_URL = 'https://smartssp-us-east.iqzone.com/pbjs'; @@ -111,6 +112,9 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let deviceWidth = 0; let deviceHeight = 0; @@ -125,7 +129,7 @@ export const spec = { winLocation = window.location; } - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; let refferLocation; try { refferLocation = refferUrl && new URL(refferUrl); diff --git a/modules/ironsourceBidAdapter.md b/modules/ironsourceBidAdapter.md deleted file mode 100644 index 86756b08809..00000000000 --- a/modules/ironsourceBidAdapter.md +++ /dev/null @@ -1,51 +0,0 @@ -#Overview - -Module Name: IronSource Bidder Adapter - -Module Type: Bidder Adapter - -Maintainer: prebid-digital-brands@ironsrc.com - - -# Description - -Module that connects to IronSource's demand sources. - -The IronSource adapter requires setup and approval from the IronSource. Please reach out to prebid-digital-brands@ironsrc.com to create an IronSource account. - -The adapter supports Video(instream). For the integration, IronSource returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction. - -# Bid Parameters -## Video - -| Name | Scope | Type | Description | Example -| ---- | ----- | ---- | ----------- | ------- -| `isOrg` | required | String | IronSource publisher Id provided by your IronSource representative | "56f91cd4d3e3660002000033" -| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 -| `ifa` | optional | String | The ID for advertisers (also referred to as "IDFA") | "XXX-XXX" -| `testMode` | optional | Boolean | This activates the test mode | false - -# Test Parameters -```javascript -var adUnits = [ - { - code: 'dfp-video-div', - sizes: [[640, 480]], - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' - } - }, - bids: [{ - bidder: 'ironsource', - params: { - isOrg: '56f91cd4d3e3660002000033', // Required - floorPrice: 2.00, // Optional - ifa: 'XXX-XXX', // Optional - testMode: false // Optional - } - }] - } - ]; -``` diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 1e3ad7b12d2..48cead83c0d 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -5,7 +5,6 @@ import { deepClone, deepSetValue, getGptSlotInfoForAdUnitCode, - hasDeviceAccess, inIframe, isArray, isEmpty, @@ -17,12 +16,12 @@ import { parseGPTSingleSizeArray, parseQueryStringParameters } from '../src/utils.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import CONSTANTS from '../src/constants.json'; -import {getStorageManager, validateStorageEnforcement} from '../src/storageManager.js'; +import {getStorageManager} from '../src/storageManager.js'; import * as events from '../src/events.js'; -import {find, includes} from '../src/polyfill.js'; +import {find} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {Renderer} from '../src/Renderer.js'; @@ -30,24 +29,24 @@ import {Renderer} from '../src/Renderer.js'; const BIDDER_CODE = 'ix'; const ALIAS_BIDDER_CODE = 'roundel'; const GLOBAL_VENDOR_ID = 10; -const MODULE_TYPE = 'bid-adapter'; -const SECURE_BID_URL = 'https://htlb.casalemedia.com/cygnus'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const SECURE_BID_URL = 'https://htlb.casalemedia.com/openrtb/pbjs'; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]; const BANNER_ENDPOINT_VERSION = 7.2; const VIDEO_ENDPOINT_VERSION = 8.1; const CENT_TO_DOLLAR_FACTOR = 100; const BANNER_TIME_TO_LIVE = 300; const VIDEO_TIME_TO_LIVE = 3600; // 1hr +const NATIVE_TIME_TO_LIVE = 3600; // Since native can have video, use ttl same as video const NET_REVENUE = true; const MAX_REQUEST_SIZE = 8000; const MAX_REQUEST_LIMIT = 4; -const OUTSTREAM_MINIMUM_PLAYER_SIZE = [300, 250]; +const OUTSTREAM_MINIMUM_PLAYER_SIZE = [144, 144]; const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; -const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; - +const IFRAME_USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; +const IMG_USER_SYNC_URL = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid'; export const ERROR_CODES = { BID_SIZE_INVALID_FORMAT: 1, BID_SIZE_NOT_INCLUDED: 2, @@ -76,7 +75,7 @@ const SOURCE_RTI_MAPPING = { 'id5-sync.com': '', // ID5 Universal ID, configured as id5Id 'crwdcntrl.net': '', // Lotame Panorama ID, lotamePanoramaId 'epsilon.com': '', // Publisher Link, publinkId - 'audigent.com': '', // Halo ID from Audigent, haloId + 'audigent.com': '', // Hadron ID from Audigent, hadronId 'pubcid.org': '', // SharedID, pubcid 'trustpid.com': '' // Trustpid }; @@ -89,7 +88,6 @@ const PROVIDERS = [ 'connectid', 'tapadId', 'quantcastId', - 'flocId', 'pubProvidedId' ]; const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration']; // note: protocol/protocols is also reqd @@ -104,6 +102,9 @@ const VIDEO_PARAMS_ALLOW_LIST = [ const LOCAL_STORAGE_KEY = 'ixdiag'; let hasRegisteredHandler = false; export const storage = getStorageManager({gvlid: GLOBAL_VENDOR_ID, bidderCode: BIDDER_CODE}); +let siteID = 0; +let gdprConsent = ''; +let usPrivacy = ''; // Possible values for bidResponse.seatBid[].bid[].mtype which indicates the type of the creative markup so that it can properly be associated with the right sub-object of the BidRequest.Imp. const MEDIA_TYPES = { @@ -111,7 +112,7 @@ const MEDIA_TYPES = { Video: 2, Audio: 3, Native: 4 -} +}; /** * Transform valid bid request config object to banner impression object that will be sent to ad server. @@ -155,6 +156,8 @@ function bidToVideoImp(bid) { } imp.video = videoParamRef ? deepClone(bid.params.video) : {}; + // populate imp level transactionId + imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid'); // copy all video properties to imp object for (const adUnitProperty in videoAdUnitRef) { @@ -178,7 +181,11 @@ function bidToVideoImp(bid) { if (context === INSTREAM) { imp.video.placement = 1; } else if (context === OUTSTREAM) { - imp.video.placement = 4; + if (deepAccess(videoParamRef, 'playerConfig.floatOnScroll')) { + imp.video.placement = 5; + } else { + imp.video.placement = 4; + } } else { logWarn(`IX Bid Adapter: Video context '${context}' is not supported`); } @@ -194,7 +201,7 @@ function bidToVideoImp(bid) { imp.ext.sid = parseGPTSingleSizeArray(impSize); } } else { - logWarn('IX Bid Adapter: Video size is missing in [mediaTypes.video] missing'); + logWarn('IX Bid Adapter: Video size is missing in [mediaTypes.video]'); return {}; } } @@ -204,6 +211,35 @@ function bidToVideoImp(bid) { return imp; } +/** + * Transform valid bid request config object to native impression object that will be sent to ad server. + * + * @param {object} bid A valid bid request config object. + * @return {object} A impression object that will be sent to ad server. + */ +function bidToNativeImp(bid) { + const imp = bidToImp(bid); + + const request = bid.nativeOrtbRequest + request.eventtrackers = [{ + event: 1, + methods: [1, 2] + }]; + request.privacy = 1; + + imp.native = { + request: JSON.stringify(request), + ver: '1.2' + }; + + // populate imp level transactionId + imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + + _applyFloor(bid, imp, NATIVE); + + return imp; +} + /** * Converts an incoming PBJS bid to an IX Impression * @param {object} bid PBJS bid object @@ -301,9 +337,19 @@ function parseBid(rawBid, currency, bidRequest) { bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; if (rawBid.mtype == MEDIA_TYPES.Video) { - bid.vastXml = rawBid.adm + bid.vastXml = rawBid.adm; } else if (rawBid.ext && rawBid.ext.vasturl) { - bid.vastUrl = rawBid.ext.vasturl + bid.vastUrl = rawBid.ext.vasturl; + } + + let parsedAdm = null; + // Detect whether the adm is (probably) JSON + if (typeof rawBid.adm === 'string' && rawBid.adm[0] === '{' && rawBid.adm[rawBid.adm.length - 1] === '}') { + try { + parsedAdm = JSON.parse(rawBid.adm); + } catch (err) { + logWarn('adm looks like JSON but failed to parse: ', err); + } } // in the event of a video @@ -313,6 +359,12 @@ function parseBid(rawBid, currency, bidRequest) { bid.mediaType = VIDEO; bid.mediaTypes = bidRequest.mediaTypes; bid.ttl = isValidExpiry ? rawBid.exp : VIDEO_TIME_TO_LIVE; + } else if (parsedAdm && parsedAdm.native) { + bid.native = {ortb: parsedAdm.native}; + bid.width = rawBid.w ? rawBid.w : 1; + bid.height = rawBid.h ? rawBid.h : 1; + bid.mediaType = NATIVE; + bid.ttl = isValidExpiry ? rawBid.exp : NATIVE_TIME_TO_LIVE; } else { bid.ad = rawBid.adm; bid.width = rawBid.w; @@ -429,6 +481,15 @@ function isValidBidFloorParams(bidFloor, bidFloorCur) { bidFloorCur.match(curRegex)); } +function nativeMediaTypeValid(bid) { + const nativeMediaTypes = deepAccess(bid, 'mediaTypes.native'); + if (nativeMediaTypes === undefined) { + return true + } + + return bid.nativeOrtbRequest && Array.isArray(bid.nativeOrtbRequest.assets) && bid.nativeOrtbRequest.assets.length > 0 +} + /** * Get bid request object with the associated id. * @@ -452,11 +513,10 @@ function getBidRequest(id, impressions, validBidRequests) { * From the userIdAsEids array, filter for the ones our adserver can use, and modify them * for our purposes, e.g. add rtiPartner * @param {array} allEids userIdAsEids passed in by prebid - * @param {object} flocId flocId passed in by prebid * @return {object} contains toSend (eids to send to the adserver) and seenSources (used to filter * identity info from IX Library) */ -function getEidInfo(allEids, flocData) { +function getEidInfo(allEids) { let toSend = []; let seenSources = {}; if (isArray(allEids)) { @@ -474,16 +534,6 @@ function getEidInfo(allEids, flocData) { } } - const isValidFlocId = flocData && flocData.id && flocData.version; - if (isValidFlocId) { - const flocEid = { - 'source': 'chrome.com', - 'uids': [{ 'id': flocData.id, 'ext': { 'rtiPartner': 'flocId', 'ver': flocData.version } }] - }; - toSend.push(flocEid); - seenSources['chrome.com'] = true; - } - return { toSend, seenSources }; } @@ -493,7 +543,7 @@ function getEidInfo(allEids, flocData) { * @param {array} validBidRequests A list of valid bid request config objects. * @param {object} bidderRequest An object containing other info like gdprConsent. * @param {object} impressions An object containing a list of impression objects describing the bids for each transactionId - * @param {array} version Endpoint version denoting banner or video. + * @param {array} version Endpoint version denoting banner, video or native. * @return {array} List of objects describing the request to the server. * */ @@ -501,9 +551,9 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { // Always use secure HTTPS protocol. let baseUrl = SECURE_BID_URL; // Get ids from Prebid User ID Modules - let eidInfo = getEidInfo(deepAccess(validBidRequests, '0.userIdAsEids'), deepAccess(validBidRequests, '0.userId.flocId')); + let eidInfo = getEidInfo(deepAccess(validBidRequests, '0.userIdAsEids')); let userEids = eidInfo.toSend; - const pageUrl = getPageUrl() || deepAccess(bidderRequest, 'refererInfo.referer'); + const pageUrl = deepAccess(bidderRequest, 'refererInfo.page'); // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded // and if the data for the partner exist @@ -528,6 +578,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } const r = {}; + const tmax = config.getConfig('bidderTimeout'); // Since bidderRequestId are the same for different bid request, just use the first one. r.id = validBidRequests[0].bidderRequestId.toString(); @@ -535,8 +586,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.ext = {}; r.ext.source = 'prebid'; r.ext.ixdiag = {}; - r.ext.ixdiag.msd = 0; - r.ext.ixdiag.msi = 0; r.imp = []; r.at = 1; @@ -546,6 +595,14 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.ext.ixdiag[key] = ixdiag[key]; } + if (tmax) { + r.ext.ixdiag.tmax = tmax; + } + + if (config.getConfig('userSync')) { + r.ext.ixdiag.syncsPerBidder = config.getConfig('userSync').syncsPerBidder; + } + // Get cached errors stored in LocalStorage const cachedErrors = getCachedErrors(); @@ -574,7 +631,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { // Apply GDPR information to the request if GDPR is enabled. if (bidderRequest) { if (bidderRequest.gdprConsent) { - const gdprConsent = bidderRequest.gdprConsent; + gdprConsent = bidderRequest.gdprConsent; if (gdprConsent.hasOwnProperty('gdprApplies')) { r.regs = { @@ -593,13 +650,14 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (gdprConsent.hasOwnProperty('addtlConsent') && gdprConsent.addtlConsent) { r.user.ext.consented_providers_settings = { consented_providers: gdprConsent.addtlConsent - } + }; } } } if (bidderRequest.uspConsent) { deepSetValue(r, 'regs.ext.us_privacy', bidderRequest.uspConsent); + usPrivacy = bidderRequest.uspConsent; } if (pageUrl) { @@ -613,8 +671,11 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { const payload = {}; // Use the siteId in the first bid request as the main siteId. - payload.s = validBidRequests[0].params.siteId; - payload.v = version; + siteID = validBidRequests[0].params.siteId; + payload.s = siteID; + if (version) { + payload.v = version; + } payload.ac = 'j'; payload.sd = 1; @@ -669,12 +730,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (typeof otherIxConfig.timeout === 'number') { payload.t = otherIxConfig.timeout; } - - if (typeof otherIxConfig.detectMissingSizes === 'boolean') { - r.ext.ixdiag.dms = otherIxConfig.detectMissingSizes; - } else { - r.ext.ixdiag.dms = true; - } } for (let adUnitIndex = 0; adUnitIndex < transactionIds.length; adUnitIndex++) { @@ -683,7 +738,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } const adUnitImpressions = impressions[transactionIds[adUnitIndex]]; - const { missingCount = 0, missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; + const { missingImps: missingBannerImpressions = [], ixImps = [] } = adUnitImpressions; let wasAdUnitImpressionsTrimmed = false; let remainingRequestSize = MAX_REQUEST_SIZE - currentRequestSize; const sourceImpressions = { ixImps, missingBannerImpressions }; @@ -700,8 +755,14 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { currentImpressionSize = encodeURIComponent(JSON.stringify({ impressionObjects })).length; } - const gpid = impressions[transactionIds[adUnitIndex]].gpid; + let gpid = impressions[transactionIds[adUnitIndex]].gpid; const dfpAdUnitCode = impressions[transactionIds[adUnitIndex]].dfp_ad_unit_code; + const tid = impressions[transactionIds[adUnitIndex]].tid; + const divId = impressions[transactionIds[adUnitIndex]].divId; + + if (!gpid && dfpAdUnitCode && divId) { + gpid = `${dfpAdUnitCode}#${divId}`; + } if (impressionObjects.length && BANNER in impressionObjects[0]) { const { id, banner: { topframe } } = impressionObjects[0]; const _bannerImpression = { @@ -710,12 +771,18 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { topframe, format: impressionObjects.map(({ banner: { w, h }, ext }) => ({ w, h, ext })) }, + }; + + const position = impressions[transactionIds[adUnitIndex]].pos; + if (isInteger(position)) { + _bannerImpression.banner.pos = position; } - if (dfpAdUnitCode || gpid) { + if (dfpAdUnitCode || gpid || tid) { _bannerImpression.ext = {}; _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; _bannerImpression.ext.gpid = gpid; + _bannerImpression.ext.tid = tid; } if ('bidfloor' in impressionObjects[0]) { @@ -727,8 +794,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } r.imp.push(_bannerImpression); - r.ext.ixdiag.msd += missingCount; - r.ext.ixdiag.msi += missingBannerImpressions.length; } else { // set imp.ext.gpid to resolved gpid for each imp impressionObjects.forEach(imp => deepSetValue(imp, 'ext.gpid', gpid)); @@ -737,7 +802,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { currentRequestSize += currentImpressionSize; - const fpd = config.getConfig('ortb2') || {}; + const fpd = deepAccess(bidderRequest, 'ortb2') || {}; if (!isEmpty(fpd) && !isFpdAdded) { r.ext.ixdiag.fpd = true; @@ -776,6 +841,21 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } } + // add identifiers info to ixDiag + const pbaAdSlot = impressions[transactionIds[adUnitIndex]].pbadslot; + const tagId = impressions[transactionIds[adUnitIndex]].tagId; + const adUnitCode = impressions[transactionIds[adUnitIndex]].adUnitCode; + if (pbaAdSlot || tagId || adUnitCode || divId) { + const clonedRObject = deepClone(r); + const requestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(clonedRObject) })}`.length; + if (requestSize < MAX_REQUEST_SIZE) { + r.ext.ixdiag.pbadslot = pbaAdSlot; + r.ext.ixdiag.tagid = tagId; + r.ext.ixdiag.adunitcode = adUnitCode; + r.ext.ixdiag.divId = divId; + } + } + const isLastAdUnit = adUnitIndex === transactionIds.length - 1; if (wasAdUnitImpressionsTrimmed || isLastAdUnit) { @@ -797,8 +877,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { currentRequestSize = baseRequestSize; r.imp = []; - r.ext.ixdiag.msd = 0; - r.ext.ixdiag.msi = 0; isFpdAdded = false; } } @@ -826,7 +904,7 @@ function _getUserIds(bidRequest) { function buildIXDiag(validBidRequests) { var adUnitMap = validBidRequests .map(bidRequest => bidRequest.transactionId) - .filter((value, index, arr) => arr.indexOf(value) === index) + .filter((value, index, arr) => arr.indexOf(value) === index); var ixdiag = { mfu: 0, @@ -896,6 +974,30 @@ function removeFromSizes(bannerSizeList, bannerSize) { } } +/** + * Creates IX Native impressions based on validBidRequests + * @param {object} validBidRequest valid request provided by prebid + * @param {object} nativeImps reference to created native impressions + */ +function createNativeImps(validBidRequest, nativeImps) { + const imp = bidToNativeImp(validBidRequest); + + if (Object.keys(imp).length != 0) { + nativeImps[validBidRequest.transactionId] = {}; + nativeImps[validBidRequest.transactionId].ixImps = []; + nativeImps[validBidRequest.transactionId].ixImps.push(imp); + nativeImps[validBidRequest.transactionId].gpid = deepAccess(validBidRequest, 'ortb2Imp.ext.gpid'); + nativeImps[validBidRequest.transactionId].dfp_ad_unit_code = deepAccess(validBidRequest, 'ortb2Imp.ext.data.adserver.adslot'); + nativeImps[validBidRequest.transactionId].pbadslot = deepAccess(validBidRequest, 'ortb2Imp.ext.data.pbadslot'); + nativeImps[validBidRequest.transactionId].tagId = deepAccess(validBidRequest, 'params.tagId'); + + const adUnitCode = validBidRequest.adUnitCode; + const divId = document.getElementById(adUnitCode) ? adUnitCode : getGptSlotInfoForAdUnitCode(adUnitCode).divId; + nativeImps[validBidRequest.transactionId].adUnitCode = adUnitCode; + nativeImps[validBidRequest.transactionId].divId = divId; + } +} + /** * Creates IX Video impressions based on validBidRequests * @param {object} validBidRequest valid request provided by prebid @@ -908,6 +1010,14 @@ function createVideoImps(validBidRequest, videoImps) { videoImps[validBidRequest.transactionId].ixImps = []; videoImps[validBidRequest.transactionId].ixImps.push(imp); videoImps[validBidRequest.transactionId].gpid = deepAccess(validBidRequest, 'ortb2Imp.ext.gpid'); + videoImps[validBidRequest.transactionId].dfp_ad_unit_code = deepAccess(validBidRequest, 'ortb2Imp.ext.data.adserver.adslot'); + videoImps[validBidRequest.transactionId].pbadslot = deepAccess(validBidRequest, 'ortb2Imp.ext.data.pbadslot'); + videoImps[validBidRequest.transactionId].tagId = deepAccess(validBidRequest, 'params.tagId'); + + const adUnitCode = validBidRequest.adUnitCode; + const divId = document.getElementById(adUnitCode) ? adUnitCode : getGptSlotInfoForAdUnitCode(adUnitCode).divId; + videoImps[validBidRequest.transactionId].adUnitCode = adUnitCode; + videoImps[validBidRequest.transactionId].divId = divId; } } @@ -918,12 +1028,6 @@ function createVideoImps(validBidRequest, videoImps) { * @param {object} bannerImps reference to created banner impressions */ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { - const DEFAULT_IX_CONFIG = { - detectMissingSizes: true, - }; - - const ixConfig = { ...DEFAULT_IX_CONFIG, ...config.getConfig('ix') }; - let imp = bidToBannerImp(validBidRequest); const bannerSizeDefined = includesSize(deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), deepAccess(validBidRequest, 'params.size')); @@ -934,45 +1038,25 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { bannerImps[validBidRequest.transactionId].gpid = deepAccess(validBidRequest, 'ortb2Imp.ext.gpid'); bannerImps[validBidRequest.transactionId].dfp_ad_unit_code = deepAccess(validBidRequest, 'ortb2Imp.ext.data.adserver.adslot'); + bannerImps[validBidRequest.transactionId].tid = deepAccess(validBidRequest, 'ortb2Imp.ext.tid'); + bannerImps[validBidRequest.transactionId].pbadslot = deepAccess(validBidRequest, 'ortb2Imp.ext.data.pbadslot'); + bannerImps[validBidRequest.transactionId].tagId = deepAccess(validBidRequest, 'params.tagId'); + bannerImps[validBidRequest.transactionId].pos = deepAccess(validBidRequest, 'mediaTypes.banner.pos'); + + const adUnitCode = validBidRequest.adUnitCode; + const divId = document.getElementById(adUnitCode) ? adUnitCode : getGptSlotInfoForAdUnitCode(adUnitCode).divId; + bannerImps[validBidRequest.transactionId].adUnitCode = adUnitCode; + bannerImps[validBidRequest.transactionId].divId = divId; // Create IX imps from params.size if (bannerSizeDefined) { if (!bannerImps[validBidRequest.transactionId].hasOwnProperty('ixImps')) { - bannerImps[validBidRequest.transactionId].ixImps = [] + bannerImps[validBidRequest.transactionId].ixImps = []; } bannerImps[validBidRequest.transactionId].ixImps.push(imp); } - if (ixConfig.hasOwnProperty('detectMissingSizes') && ixConfig.detectMissingSizes) { - updateMissingSizes(validBidRequest, missingBannerSizes, imp); - } -} - -/** - * Returns the `pageUrl` set by publisher on the page if it is an valid url - */ -function getPageUrl() { - const pageUrl = config.getConfig('pageUrl'); - try { - const url = new URL(pageUrl); - return url.href; - } catch (_) { - logWarn(`IX Bid Adapter: invalid pageUrl config property value set: ${pageUrl}`); - return undefined; - } -} - -/** - * Determines IX configuration type based on IX params - * @param {object} valid IX configured param - * @returns {string} - */ -function detectParamsType(validBidRequest) { - if (deepAccess(validBidRequest, 'mediaTypes.video') && bidToVideoImp(validBidRequest).video) { - return VIDEO; - } - - return BANNER; + updateMissingSizes(validBidRequest, missingBannerSizes, imp); } /** @@ -1083,15 +1167,7 @@ function storeErrorEventData(data) { */ function localStorageHandler(data) { if (data.type === 'ERROR' && data.arguments && data.arguments[1] && data.arguments[1].bidder === BIDDER_CODE) { - const DEFAULT_ENFORCEMENT_SETTINGS = { - hasEnforcementHook: false, - valid: hasDeviceAccess() - }; - validateStorageEnforcement(GLOBAL_VENDOR_ID, BIDDER_CODE, MODULE_TYPE, DEFAULT_ENFORCEMENT_SETTINGS, (permissions) => { - if (permissions.valid) { - storeErrorEventData(data); - } - }); + storeErrorEventData(data); } } @@ -1188,6 +1264,7 @@ function isIndexRendererPreferred(bid) { } const isValid = !!(typeof (renderer) === 'object' && renderer.url && renderer.render); + // if renderer on the adunit is not valid or it's only a backup, our renderer may be used return !isValid || renderer.backupOnly; } @@ -1267,7 +1344,17 @@ export const spec = { } if (mediaTypeVideoRef && paramsVideoRef) { + const videoImp = bidToVideoImp(bid).video; const errorList = checkVideoParams(mediaTypeVideoRef, paramsVideoRef); + if (deepAccess(bid, 'mediaTypes.video.context') === OUTSTREAM && isIndexRendererPreferred(bid) && videoImp) { + const outstreamPlayerSize = [deepAccess(videoImp, 'w'), deepAccess(videoImp, 'h')]; + const isValidSize = outstreamPlayerSize[0] >= OUTSTREAM_MINIMUM_PLAYER_SIZE[0] && outstreamPlayerSize[1] >= OUTSTREAM_MINIMUM_PLAYER_SIZE[1]; + if (!isValidSize) { + logError(`IX Bid Adapter: ${outstreamPlayerSize} is an invalid size for IX outstream renderer`); + return false; + } + } + if (errorList.length) { errorList.forEach((err) => { logError(err, { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); @@ -1276,17 +1363,7 @@ export const spec = { } } - const videoImp = bidToVideoImp(bid).video; - if (deepAccess(bid, 'mediaTypes.video.context') === OUTSTREAM && isIndexRendererPreferred(bid) && videoImp) { - const outstreamPlayerSize = deepAccess(videoImp, 'playerSize')[0]; - const isValidSize = outstreamPlayerSize[0] >= OUTSTREAM_MINIMUM_PLAYER_SIZE[0] && outstreamPlayerSize[1] >= OUTSTREAM_MINIMUM_PLAYER_SIZE[1]; - if (!isValidSize) { - logError(`IX Bid Adapter: ${mediaTypeVideoPlayerSize} is an invalid size for IX outstream renderer`); - return false; - } - } - - return true; + return nativeMediaTypeValid(bid); }, /** @@ -1300,41 +1377,32 @@ export const spec = { const reqs = []; // Stores banner + video requests const bannerImps = {}; // Stores created banner impressions const videoImps = {}; // Stores created video impressions - const multiFormatAdUnits = {}; // Stores references identified multi-format adUnits + const nativeImps = {}; // Stores created native impressions const missingBannerSizes = {}; // To capture the missing sizes i.e not configured for ix // Step 1: Create impresssions from IX params validBidRequests.forEach((validBidRequest) => { const adUnitMediaTypes = Object.keys(deepAccess(validBidRequest, 'mediaTypes', {})) - switch (detectParamsType(validBidRequest)) { - case BANNER: - createBannerImps(validBidRequest, missingBannerSizes, bannerImps); - break; - case VIDEO: - createVideoImps(validBidRequest, videoImps) - break; - } - - if (includes(adUnitMediaTypes, BANNER) && includes(adUnitMediaTypes, VIDEO)) { - multiFormatAdUnits[validBidRequest.transactionId] = validBidRequest; - } - }); - - // Step 2: Create impressions for multi-format adunits missing configurations - Object.keys(multiFormatAdUnits).forEach((transactionId) => { - const validBidRequest = multiFormatAdUnits[transactionId]; - if (!bannerImps[transactionId]) { - createBannerImps(validBidRequest, missingBannerSizes, bannerImps); - } - - if (!videoImps[transactionId]) { - createVideoImps(validBidRequest, videoImps) + for (const type in adUnitMediaTypes) { + switch (adUnitMediaTypes[type]) { + case BANNER: + createBannerImps(validBidRequest, missingBannerSizes, bannerImps); + break; + case VIDEO: + createVideoImps(validBidRequest, videoImps) + break; + case NATIVE: + createNativeImps(validBidRequest, nativeImps) + break; + default: + logWarn(`IX Bid Adapter: ad unit mediaTypes ${type} is not supported`) + } } }); - // Step 3: Update banner impressions with missing sizes - for (var transactionId in missingBannerSizes) { + // Step 2: Update banner impressions with missing sizes + for (let transactionId in missingBannerSizes) { if (missingBannerSizes.hasOwnProperty(transactionId)) { let missingSizes = missingBannerSizes[transactionId].missingSizes; @@ -1355,13 +1423,16 @@ export const spec = { } } - // Step 4: Build banner & video requests + // Step 4: Build banner, video & native requests if (Object.keys(bannerImps).length > 0) { reqs.push(...buildRequest(validBidRequests, bidderRequest, bannerImps, BANNER_ENDPOINT_VERSION)); } if (Object.keys(videoImps).length > 0) { reqs.push(...buildRequest(validBidRequests, bidderRequest, videoImps, VIDEO_ENDPOINT_VERSION)); } + if (Object.keys(nativeImps).length > 0) { + reqs.push(...buildRequest(validBidRequests, bidderRequest, nativeImps)); + } return reqs; }, @@ -1440,11 +1511,60 @@ export const spec = { * @returns {array} User sync pixels */ getUserSyncs: function (syncOptions, serverResponses) { - return (syncOptions.iframeEnabled) ? [{ - type: 'iframe', - url: USER_SYNC_URL - }] : []; + const syncs = []; + let publisherSyncsPerBidderOverride = null; + if (serverResponses.length > 0) { + publisherSyncsPerBidderOverride = deepAccess(serverResponses[0], 'body.ext.publishersyncsperbidderoverride'); + } + if (publisherSyncsPerBidderOverride !== undefined && publisherSyncsPerBidderOverride == 0) { + return []; + } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: IFRAME_USER_SYNC_URL + }) + } else { + let publisherSyncsPerBidder = null; + if (config.getConfig('userSync')) { + publisherSyncsPerBidder = config.getConfig('userSync').syncsPerBidder + } + if (publisherSyncsPerBidder === 0) { + publisherSyncsPerBidder = publisherSyncsPerBidderOverride + } + if (publisherSyncsPerBidderOverride && (publisherSyncsPerBidder === 0 || publisherSyncsPerBidder)) { + publisherSyncsPerBidder = publisherSyncsPerBidderOverride > publisherSyncsPerBidder ? publisherSyncsPerBidder : publisherSyncsPerBidderOverride + } else { + publisherSyncsPerBidder = 1 + } + for (let i = 0; i < publisherSyncsPerBidder; i++) { + syncs.push({ + type: 'image', + url: buildImgSyncUrl(publisherSyncsPerBidder, i) + }) + } + } + return syncs; } }; +/** + * Build img user sync url + * @param {int} syncsPerBidder number of syncs Per Bidder + * @param {int} index index to pass + * @returns {string} img user sync url + */ +function buildImgSyncUrl(syncsPerBidder, index) { + let consentString = ''; + let gdprApplies = '0'; + if (gdprConsent && gdprConsent.hasOwnProperty('gdprApplies')) { + gdprApplies = gdprConsent.gdprApplies ? '1' : '0'; + } + if (gdprConsent && gdprConsent.hasOwnProperty('consentString')) { + consentString = gdprConsent.consentString || ''; + } + + return IMG_USER_SYNC_URL + '&site_id=' + siteID.toString() + '&p=' + syncsPerBidder.toString() + '&i=' + index.toString() + '&gdpr=' + gdprApplies + '&gdpr_consent=' + consentString + '&us_privacy=' + (usPrivacy || ''); +} + registerBidder(spec); diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index 415fdc9db65..638cb11c5ab 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -13,7 +13,7 @@ Description This module connects publishers to Index Exchange's (IX) network of demand sources through Prebid.js. This module is GDPR and CCPA compliant. -It is compatible with the newer PrebidJS 5.0 ad unit format where the `banner` and/or `video` properties are encapsulated within the `adUnits[].mediaTypes` object. We +It is compatible with the newer PrebidJS 5.0 ad unit format where the `banner`, `video` and/or `native` properties are encapsulated within the `adUnits[].mediaTypes` object. We recommend that you use this newer format when possible as it will be better able to accommodate new feature additions. @@ -32,7 +32,18 @@ var adUnits = [{ video: { context: 'instream', playerSize: [300, 250] - } + }, + native: { + title: { + required: true + }, + image: { + required: true + }, + body: { + required: false + } + }, }, // ... }]; @@ -44,7 +55,7 @@ var adUnits = [{ | --- | --- | Banner | Fully supported for all IX approved sizes. | Video | Fully supported for all IX approved sizes. -| Native | Not supported. +| Native | Supported (render through GAM or publisher's renderer). @@ -56,14 +67,14 @@ Each of the IX-specific parameters provided under the `adUnits[].bids[].params` object are detailed here. -### Banner +## Banner | Key | Scope | Type | Description | --- | --- | --- | --- | siteId | Required | String | An IX-specific identifier that is associated with this ad unit. It will be associated to the single size, if the size provided. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` | sizes | Required | Number[Number[]] | The size / sizes associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].mediaTypes.banner.sizes`. Examples: `[300, 250]`, `[300, 600]`, `[728, 90]` -### Video +## Video | Key | Scope | Type | Description | --- | --- | --- | --- @@ -77,6 +88,61 @@ object are detailed here. |video.minduration| Required | Integer | Minimum video ad duration in seconds. |video.maxduration| Required | Integer | Maximum video ad duration in seconds. |video.protocol / video.protocols| Required | Integer / Integer[] | Either a single protocol provided as an integer, or protocols provided as a list of integers. `2` - VAST 2.0, `3` - VAST 3.0, `5` - VAST 2.0 Wrapper, `6` - VAST 3.0 Wrapper +| video.playerConfig | Optional | Hash | The Index specific outstream player configurations. +| video.playerConfig.floatOnScroll | Optional | Boolean | A boolean specifying whether you want to use the player’s floating capabilities, where:
- `true`: Allow the player to float.
Note: If you set floatOnScroll to true, Index updates the placement value to `5`.
- `false`: Do not allow the player to float (default). +| video.playerConfig.floatSize | Optional | Integer[] | The height and width of the floating player in pixels. If you do not specify a float size, the player adjusts to the aspect ratio of the player size that is defined when it is not floating. Index recommends that you review and test the float size to your user experience preference. + +## Native + +| Key | Scope | Type | Description +| --- | --- | --- | --- +| sendTargetingKeys | Optional | Boolean | Defines whether or not to send the hb_native_ASSET targeting keys to the ad server. Defaults to true. +| adTemplate | Optional | String | Used in the ‘AdUnit-Defined Creative Scenario’, this value controls the Native template right in the page. +| rendererUrl | Optional | String | Used in the ‘Custom Renderer Scenario’, this points to javascript code that will produce the Native template. +| title | Optional | Title asset | The title of the ad, usually a call to action or a brand name. +| body | Optional | Data asset | Text of the ad copy. +| body2 | Optional | Data asset | Additional Text of the ad copy. +| sponsoredBy | Optional | Data asset | The name of the brand associated with the ad. +| icon | Optional | Image asset | The brand icon that will appear with the ad. +| image | Optional | Image asset | A picture that is associated with the brand, or grabs the user’s attention. +| displayUrl | Optional | Data asset | Text that can be displayed instead of the raw click URL. e.g, “Example.com/Specials” +| cta | Optional | Data asset | Call to Action text, e.g., “Click here for more information”. +| rating | Optional | Data asset | Rating information, e.g., “4” out of 5. +| downloads | Optional | Data asset | The total downloads of the advertised application/product. +| likes | Optional | Data asset | The total number of individuals who like the advertised application/product. +| price | Optional | Data asset | The non-sale price of the advertised application/product. +| salePrice | Optional | Data asset | The sale price of the advertised application/product. +| address | Optional | Data asset | Address of the Buyer/Store. e.g, “123 Main Street, Anywhere USA” +| phone | Optional | Data asset | Phone Number of the Buyer/Store. e.g, “(123) 456-7890” + +#### Title Asset + +| Key | Scope | Type | Description +| --- | --- | --- | --- +| required | Required | Boolean | Specify whether or not a title is required. +| len | Optional | Integer | Maximum number of characters (defaults to 25 if omitted). +| ext | Optional | Object | Arbitrary additional parameters to send to the bidder. + +#### Data Asset + +| Key | Scope | Type | Description +| --- | --- | --- | --- +| required | Required | Boolean | Specify whether or not the asset is required. +| len | Optional | Integer | Maximum number of characters. +| ext | Optional | Object | Arbitrary additional parameters to send to the bidder. + +#### Image Asset + +| Key | Scope | Type | Description +| --- | --- | --- | --- +| required | Required | Boolean | Specify whether or not the asset is required. +| sizes | Optional | Integer[] | Minimum size requested for the image, e.g., [100, 100] +| mimes | Optional | String[] | Whitelist of content MIME types supported, e.g., ["image/jpg", "image/gif"] +| ext | Optional | Object | Arbitrary additional parameters to send to the bidder. + +### Rendering Native Ad + +Native ad can be rendered by setting up GAM or setup custom native renderer. Reference [Prebid implementing the native template](https://docs.prebid.org/prebid/native-implementation.html#4-implementing-the-native-template) for more information. ## Deprecation warning @@ -247,13 +313,19 @@ var adUnits = [{ minduration: 5, maxduration: 30, mimes: ['video/mp4', 'application/javascript'], - placement: 3 + placement: 5 } }, bids: [{ bidder: 'ix', params: { siteId: '715964' + video: { + playerConfig: { + floatOnScroll: true, + floatSize: [300,250] + } + } } }] }]; @@ -290,24 +362,33 @@ pbjs.setConfig({ }); ``` #### The **detectMissingSizes** feature -By default, the IX bidding adapter bids on all banner sizes available in the ad unit when configured to at least one banner size. If you want the IX bidding adapter to only bid on the banner size it’s configured to, switch off this feature using `detectMissingSizes`. -``` -pbjs.setConfig({ - ix: { - detectMissingSizes: false - } -}); -``` -OR -``` -pbjs.setBidderConfig({ - bidders: ["ix"], - config: { - ix: { - detectMissingSizes: false +`detectMissingSize` config is now deprecated and IX bidding adapter bids on all banner sizes available in the ad unit when configured to at least one banner size. + +**Native** + +```javascript +var adUnits = [{ + code: 'native-div-a', + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: false + } } - } -}); + }, + bids: [{ + bidder: 'ix', + params: { + siteId: '715966' + } + }] +}]; ``` ### 2. Include `ixBidAdapter` in your build process @@ -403,11 +484,8 @@ to `'ix'` across all ad units that bids are being requested for does not exceed ### Time-To-Live (TTL) -Banner bids from IX have a TTL of 300 seconds while video bids have a TTL of 1 hour, after which time they become -invalid. - -If an invalid bid wins, and its associated ad is rendered, it will not count -towards total impressions on IX's side. +Banner bids from Index have a TTL of 600 seconds while video bids have a TTL of three hours, after which time they become invalid. +**Note:** Index supports the `bid.exp` attribute in the bid response which allows our adapter to specify the maximum number of seconds allowed between the auction and billing notice. In the absence of the `bid.exp` attribute, the TTL provided above applies. FAQs ==== @@ -423,4 +501,4 @@ The `size` parameter is no longer a required field, the `siteId` will now be ass In your browser of choice, create a new tab and open the developer tools. In developer tools, select the network tab. Then, navigate to a page where IX is setup to bid. Now, in the network tab, search for requests to -`casalemedia.com/cygnus`. These are the bid requests. +`casalemedia.com/openrtb/pbjs`. These are the bid requests. diff --git a/modules/jcmBidAdapter.md b/modules/jcmBidAdapter.md deleted file mode 100644 index 53a2356df2f..00000000000 --- a/modules/jcmBidAdapter.md +++ /dev/null @@ -1,40 +0,0 @@ -#Overview - -``` -Module Name: JCM Bidder Adapter -Module Type: Bidder Adapter -Maintainer: george@jcartermarketing.com -``` - -# Description - -Module that connects to J Carter Marketing demand sources - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div1', - sizes: [[300, 250]], // display 300x250 - bids: [ - { - bidder: 'jcm', - params: { - siteId: '3608' - } - } - ] - },{ - code: 'test-div2', - sizes: [[728, 90]], // display 728x90 - bids: [ - { - bidder: 'jcm', - params: { - siteId: '3608' - } - } - ] - } - ]; - diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index fb55add910f..f6f58883990 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -1,11 +1,11 @@ -import { logWarn, parseUrl, deepAccess, isArray, getDNT } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { ajax } from '../src/ajax.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { Renderer } from '../src/Renderer.js'; +import {deepAccess, getDNT, isArray, logWarn} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {ajax} from '../src/ajax.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {Renderer} from '../src/Renderer.js'; import {createEidsArray} from './userId/eids.js'; const BIDDER_CODE = 'jixie'; @@ -28,14 +28,14 @@ function setIds_(clientId, sessionId) { let expC = (new Date(new Date().setFullYear(new Date().getFullYear() + 1))).toUTCString(); let expS = (new Date(new Date().setMinutes(new Date().getMinutes() + sidTTLMins_))).toUTCString(); - storage.setCookie('_jx', clientId, expC, 'None', null); - storage.setCookie('_jx', clientId, expC, 'None', dd); + storage.setCookie('_jxx', clientId, expC, 'None', null); + storage.setCookie('_jxx', clientId, expC, 'None', dd); - storage.setCookie('_jxs', sessionId, expS, 'None', null); - storage.setCookie('_jxs', sessionId, expS, 'None', dd); + storage.setCookie('_jxxs', sessionId, expS, 'None', null); + storage.setCookie('_jxxs', sessionId, expS, 'None', dd); - storage.setDataInLocalStorage('_jx', clientId); - storage.setDataInLocalStorage('_jxs', sessionId); + storage.setDataInLocalStorage('_jxx', clientId); + storage.setDataInLocalStorage('_jxxs', sessionId); } catch (error) {} } @@ -47,14 +47,14 @@ function fetchIds_() { session_id_ls: '' }; try { - let tmp = storage.getCookie('_jx'); + let tmp = storage.getCookie('_jxx'); if (tmp) ret.client_id_c = tmp; - tmp = storage.getCookie('_jxs'); + tmp = storage.getCookie('_jxxs'); if (tmp) ret.session_id_c = tmp; - tmp = storage.getDataFromLocalStorage('_jx'); + tmp = storage.getDataFromLocalStorage('_jxx'); if (tmp) ret.client_id_ls = tmp; - tmp = storage.getDataFromLocalStorage('_jxs'); + tmp = storage.getDataFromLocalStorage('_jxxs'); if (tmp) ret.session_id_ls = tmp; } catch (error) {} return ret; @@ -116,10 +116,12 @@ function getMiscDims_() { mkeywords: '' } try { + // TODO: this should pick refererInfo from bidderRequest let refererInfo_ = getRefererInfo(); - let url_ = ((refererInfo_ && refererInfo_.referer) ? refererInfo_.referer : window.location.href); + // TODO: does the fallback make sense here? + let url_ = refererInfo_?.page || window.location.href ret.pageurl = url_; - ret.domain = parseUrl(url_).host; + ret.domain = refererInfo_?.domain || window.location.host ret.device = getDevice_(); let keywords = document.getElementsByTagName('meta')['keywords']; if (keywords && keywords.content) { diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js index e2ba92d51d9..7f154614e4d 100644 --- a/modules/justpremiumBidAdapter.js +++ b/modules/justpremiumBidAdapter.js @@ -25,7 +25,8 @@ export const spec = { }).filter((value, index, self) => { return self.indexOf(value) === index }), - referer: bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + referer: bidderRequest.refererInfo.page, sw: dim.screenWidth, sh: dim.screenHeight, ww: dim.innerWidth, diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 6cccd660854..342531ba26e 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -12,7 +12,7 @@ import {submodule} from '../src/hook.js'; import {config} from '../src/config.js'; import {ajaxBuilder} from '../src/ajax.js'; -import {logError} from '../src/utils.js'; +import {deepAccess, logError} from '../src/utils.js'; import {find} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; @@ -130,7 +130,7 @@ function onRequestCompleted(mediaID, success) { function enrichBidRequest(bidReqConfig, onDone) { activeRequestCount = 0; const adUnits = bidReqConfig.adUnits || getGlobal().adUnits; - enrichAdUnits(adUnits); + enrichAdUnits(adUnits, bidReqConfig.ortb2Fragments); if (activeRequestCount <= 0) { onDone(); } else { @@ -142,10 +142,10 @@ function enrichBidRequest(bidReqConfig, onDone) { * get targeting data and write to bids * @function * @param {adUnit[]} adUnits - * @param {function} onDone + * @param ortb2Fragments */ -export function enrichAdUnits(adUnits) { - const fpdFallback = config.getConfig('ortb2.site.ext.data.jwTargeting'); +export function enrichAdUnits(adUnits, ortb2Fragments = {}) { + const fpdFallback = deepAccess(ortb2Fragments.global, 'site.ext.data.jwTargeting'); adUnits.forEach(adUnit => { const jwTargeting = extractPublisherParams(adUnit, fpdFallback); if (!jwTargeting || !Object.keys(jwTargeting).length) { @@ -162,11 +162,7 @@ export function enrichAdUnits(adUnits) { const contentData = getContentData(mediaId, contentSegments); const targeting = formatTargetingResponse(vat); enrichBids(adUnit.bids, targeting, contentId, contentData); - let ortb2 = config.getConfig('ortb2'); - ortb2 = getOrtbSiteContent(ortb2, contentId, contentData); - if (ortb2) { - config.setConfig({ ortb2 }); - } + addOrtbSiteContent(ortb2Fragments.global, contentId, contentData); }; loadVat(jwTargeting, onVatResponse); }); @@ -309,12 +305,12 @@ export function getContentData(mediaId, segments) { return contentData; } -export function getOrtbSiteContent(ortb2, contentId, contentData) { +export function addOrtbSiteContent(ortb2, contentId, contentData) { if (!contentId && !contentData) { return; } - if (!ortb2) { + if (ortb2 == null) { ortb2 = {}; } @@ -345,10 +341,6 @@ function enrichBids(bids, targeting, contentId, contentData) { bids.forEach(bid => { addTargetingToBid(bid, targeting); - const ortb2 = getOrtbSiteContent(bid.ortb2, contentId, contentData); - if (ortb2) { - bid.ortb2 = ortb2; - } }); } diff --git a/modules/kargoAnalyticsAdapter.js b/modules/kargoAnalyticsAdapter.js index 83c20846c0d..652e105167d 100644 --- a/modules/kargoAnalyticsAdapter.js +++ b/modules/kargoAnalyticsAdapter.js @@ -1,14 +1,96 @@ -import adapter from '../src/AnalyticsAdapter.js'; +import { logError } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; -var kargoAdapter = adapter({ - analyticsType: 'endpoint', - url: 'https://krk.kargo.com/api/v1/event/prebid' -}); +const EVENT_URL = 'https://krk.kargo.com/api/v1/event'; +const KARGO_BIDDER_CODE = 'kargo'; + +const analyticsType = 'endpoint'; + +let _initOptions = {}; + +let _logBidResponseData = { + auctionId: '', + auctionTimeout: 0, + responseTime: 0, +}; + +let _bidResponseDataLogged = []; + +var kargoAnalyticsAdapter = Object.assign( + adapter({ analyticsType }), { + track({ eventType, args }) { + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: { + _logBidResponseData.auctionTimeout = args.timeout; + break; + } + case CONSTANTS.EVENTS.BID_RESPONSE: { + handleBidResponseData(args); + break; + } + } + } + } +); + +// handleBidResponseData: Get auction data for Kargo bids and send to server +function handleBidResponseData (bidResponse) { + // Verify this is Kargo and we haven't seen this auction data yet + if (bidResponse.bidder !== KARGO_BIDDER_CODE || _bidResponseDataLogged.includes(bidResponse.auctionId) !== false) { + return + } + + _logBidResponseData.auctionId = bidResponse.auctionId; + _logBidResponseData.responseTime = bidResponse.timeToRespond; + sendAuctionData(_logBidResponseData); +} + +// sendAuctionData: Send auction data to the server +function sendAuctionData (data) { + try { + _bidResponseDataLogged.push(data.auctionId); + + if (!shouldFireEventRequest()) { + return; + } + + ajax( + `${EVENT_URL}/auction-data`, + null, + { + aid: data.auctionId, + ato: data.auctionTimeout, + rt: data.responseTime, + it: 0, + }, + { + method: 'GET', + } + ); + } catch (err) { + logError('Error sending auction data: ', err); + } +} + +// Sampling rate out of 100 +function shouldFireEventRequest () { + const samplingRate = (_initOptions && _initOptions.sampling) || 100; + return ((Math.floor(Math.random() * 100) + 1) <= parseInt(samplingRate)); +} + +kargoAnalyticsAdapter.originEnableAnalytics = kargoAnalyticsAdapter.enableAnalytics; + +kargoAnalyticsAdapter.enableAnalytics = function (config) { + _initOptions = config.options; + kargoAnalyticsAdapter.originEnableAnalytics(config); +}; adapterManager.registerAnalyticsAdapter({ - adapter: kargoAdapter, + adapter: kargoAnalyticsAdapter, code: 'kargo' }); -export default kargoAdapter; +export default kargoAnalyticsAdapter; diff --git a/modules/kargoAnalyticsAdapter.md b/modules/kargoAnalyticsAdapter.md new file mode 100644 index 00000000000..5a1e538902a --- /dev/null +++ b/modules/kargoAnalyticsAdapter.md @@ -0,0 +1,33 @@ +# Overview + +Module Name: Kargo Analytics Adapter +Module Type: Analytics Adapter +Maintainer: support@kargo.com + +# Description + +Analytics adapter for Kargo. Contact support@kargo.com for information. + +# Usage + +The simplest way to enable the analytics adapter is this + +```javascript +pbjs.enableAnalytics([{ + provider: 'kargo', + options: { + sampling: 100 // value out of 100 + } +}]); +``` + +# Test Parameters + +``` +{ + provider: 'kargo', + options: { + sampling: 100 + } +} +``` \ No newline at end of file diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 80b3d83167e..f4563081aa7 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -9,7 +9,7 @@ const HOST = 'https://krk.kargo.com'; const SYNC = 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}'; const SYNC_COUNT = 5; const GVLID = 972; -const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO] +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); let sessionId, @@ -60,7 +60,14 @@ export const spec = { height: window.screen.height }, prebidRawBidRequests: validBidRequests - }, spec._getAllMetadata(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent)); + }, spec._getAllMetadata(bidderRequest, tdid)); + + // Pull Social Canvas segments and embed URL + if (validBidRequests.length > 0 && validBidRequests[0].params.socialCanvas) { + transformedParams.socialCanvasSegments = validBidRequests[0].params.socialCanvas.segments; + transformedParams.socialEmbedURL = validBidRequests[0].params.socialCanvas.embedURL; + } + const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); return Object.assign({}, bidderRequest, { method: 'GET', @@ -88,6 +95,7 @@ export const spec = { } const bidResponse = { + ad: adUnit.adm, requestId: bidId, cpm: Number(adUnit.cpm), width: adUnit.width, @@ -103,8 +111,6 @@ export const spec = { if (meta.mediaType == VIDEO) { bidResponse.vastXml = adUnit.adm; - } else { - bidResponse.ad = adUnit.adm; } bidResponses.push(bidResponse); @@ -147,31 +153,9 @@ export const spec = { }); }, - // PRIVATE - _readCookie(name) { - if (!storage.cookiesAreEnabled()) { - return null; - } - let nameEquals = `${name}=`; - let cookies = document.cookie.split(';'); - - for (let i = 0; i < cookies.length; i++) { - let cookie = cookies[i]; - while (cookie.charAt(0) === ' ') { - cookie = cookie.substring(1, cookie.length); - } - - if (cookie.indexOf(nameEquals) === 0) { - return cookie.substring(nameEquals.length, cookie.length); - } - } - - return null; - }, - _getCrbFromCookie() { try { - const crb = JSON.parse(decodeURIComponent(spec._readCookie('krg_crb'))); + const crb = JSON.parse(storage.getCookie('krg_crb')); if (crb && crb.v) { let vParsed = JSON.parse(atob(crb.v)); if (vParsed) { @@ -238,11 +222,11 @@ export const spec = { return crb.clientId; }, - _getAllMetadata(tdid, usp, gdpr) { + _getAllMetadata(bidderRequest, tdid) { return { - userIDs: spec._getUserIds(tdid, usp, gdpr), - pageURL: window.location.href, - rawCRB: spec._readCookie('krg_crb'), + userIDs: spec._getUserIds(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent), + pageURL: bidderRequest.refererInfo && bidderRequest.refererInfo.page, + rawCRB: storage.getCookie('krg_crb'), rawCRBLocalStorage: spec._getLocalStorageSafely('krg_crb') }; }, diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 5fc28c47ac5..1dc22d0099a 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -102,21 +102,23 @@ export const onTimeout = function (timeoutDataArray) { } }; -function getPageUrlFromRefererInfo() { - const refererInfo = getRefererInfo(); - return (refererInfo && refererInfo.referer) - ? refererInfo.referer - : window.location.href; -} - function getPageUrlFromRequest(validBidRequest, bidderRequest) { // pageUrl is considered only when testing to ensure that non-test requests always contain the correct URL if (isTest(validBidRequest) && config.getConfig('pageUrl')) { + // TODO: it's not clear what the intent is here - but all adapters should always respect pageUrl. + // With prebid 7, using `refererInfo.page` will do that automatically. return config.getConfig('pageUrl'); } - return (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) - ? bidderRequest.refererInfo.referer + return (bidderRequest.refererInfo && bidderRequest.refererInfo.page) + ? bidderRequest.refererInfo.page + : window.location.href; +} + +function getPageUrlFromRefererInfo() { + const refererInfo = getRefererInfo(); + return (refererInfo && refererInfo.page) + ? refererInfo.page : window.location.href; } diff --git a/modules/komoonaBidAdapter.md b/modules/komoonaBidAdapter.md deleted file mode 100644 index 6f88c19dfa6..00000000000 --- a/modules/komoonaBidAdapter.md +++ /dev/null @@ -1,29 +0,0 @@ -# Overview - -**Module Name**: Komoona Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: support@komoona.com - -# Description - -Connects to Komoona demand source to fetch bids. - -# Test Parameters -``` - var adUnits = [{ - code: 'banner-ad-div', - sizes: [[300, 250]], - - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'komoona', - params: { - placementId: 'e69148e0ba6c4c07977dc2daae5e1577', - hbid: '1f5b2c10e66e419580bd943b9af692ab', - floorPrice: 0.5 - } - }] - }]; -``` - - diff --git a/modules/konduitAnalyticsAdapter.js b/modules/konduitAnalyticsAdapter.js index f8a44d7cc94..a1a586b25db 100644 --- a/modules/konduitAnalyticsAdapter.js +++ b/modules/konduitAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { parseSizesInput, logError, uniques } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { targeting } from '../src/targeting.js'; import { config } from '../src/config.js'; diff --git a/modules/krushmediaBidAdapter.js b/modules/krushmediaBidAdapter.js index da68bddcb7b..876f0ebabc6 100644 --- a/modules/krushmediaBidAdapter.js +++ b/modules/krushmediaBidAdapter.js @@ -1,6 +1,7 @@ import { isFn, deepAccess, logMessage } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'krushmedia'; const AD_URL = 'https://ads4.krushmedia.com/?c=rtb&m=hb'; @@ -49,10 +50,14 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let winTop = window; let location; + // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin try { - location = new URL(bidderRequest.refererInfo.referer) + location = new URL(bidderRequest.refererInfo.page); winTop = window.top; } catch (e) { location = winTop.location; @@ -75,7 +80,7 @@ export const spec = { request.ccpa = bidderRequest.uspConsent; } if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent + request.gdpr = bidderRequest.gdprConsent; } } diff --git a/modules/kubientBidAdapter.js b/modules/kubientBidAdapter.js index 46360572576..57cbe6acd07 100644 --- a/modules/kubientBidAdapter.js +++ b/modules/kubientBidAdapter.js @@ -67,8 +67,9 @@ export const spec = { data.coppa = 1 } - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - data.referer = bidderRequest.refererInfo.referer + if (bidderRequest?.refererInfo?.page) { + // TODO: is 'page' the right value here? + data.referer = bidderRequest.refererInfo.page } if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { @@ -151,15 +152,7 @@ function encodeQueryData(data) { function kubientGetConsentGiven(gdprConsent) { let consentGiven = 0; if (typeof gdprConsent !== 'undefined') { - let apiVersion = deepAccess(gdprConsent, `apiVersion`); - switch (apiVersion) { - case 1: - consentGiven = deepAccess(gdprConsent, `vendorData.vendorConsents.${VENDOR_ID}`) ? 1 : 0; - break; - case 2: - consentGiven = deepAccess(gdprConsent, `vendorData.vendor.consents.${VENDOR_ID}`) ? 1 : 0; - break; - } + consentGiven = deepAccess(gdprConsent, `vendorData.vendor.consents.${VENDOR_ID}`) ? 1 : 0; } return consentGiven; } diff --git a/modules/kueezBidAdapter.js b/modules/kueezBidAdapter.js new file mode 100644 index 00000000000..24d339d1938 --- /dev/null +++ b/modules/kueezBidAdapter.js @@ -0,0 +1,471 @@ +import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_ENDPOINT = 'https://hb.kueezssp.com/hb-kz-multi'; +const BIDDER_TEST_ENDPOINT = 'https://hb.kueezssp.com/hb-multi-kz-test' +const BIDDER_CODE = 'kueez'; +const MAIN_CURRENCY = 'USD'; +const MEDIA_TYPES = [BANNER, VIDEO]; +const TTL = 420; +const VERSION = '1.0.0'; +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} + +export const spec = { + code: BIDDER_CODE, + version: VERSION, + supportedMediaTypes: MEDIA_TYPES, + isBidRequestValid: function (bidRequest) { + return validateParams(bidRequest); + }, + buildRequests: function (validBidRequests, bidderRequest) { + const [ sharedParams ] = validBidRequests; + const testMode = sharedParams.params.testMode; + const bidsToSend = prepareBids(validBidRequests, sharedParams, bidderRequest); + + return { + method: 'POST', + url: getBidderEndpoint(testMode), + data: bidsToSend + } + }, + interpretResponse: function ({body}) { + const bidResponses = body?.bids; + + if (!bidResponses || !bidResponses.length) { + return []; + } + + return parseBidResponses(bidResponses); + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + const pixels = response.body.params.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }) + syncs.push(...pixels) + } + if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + syncs.push({ + type: 'iframe', + url: response.body.params.userSyncURL + }); + } + } + return syncs; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } + + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } + } +}; + +registerBidder(spec); + +/** + * Get schain string value + * @param schainObject {Object} + * @returns {string} + */ +function getSupplyChain(schainObject) { + if (isEmpty(schainObject)) { + return ''; + } + let scStr = `${schainObject.ver},${schainObject.complete}`; + schainObject.nodes.forEach((node) => { + scStr += '!'; + scStr += `${getEncodedValIfNotEmpty(node.asi)},`; + scStr += `${getEncodedValIfNotEmpty(node.sid)},`; + scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; + scStr += `${getEncodedValIfNotEmpty(node.rid)},`; + scStr += `${getEncodedValIfNotEmpty(node.name)},`; + scStr += `${getEncodedValIfNotEmpty(node.domain)}`; + }); + return scStr; +} + +/** + * Get the encoded value + * @param val {string} + * @returns {string} + */ +function getEncodedValIfNotEmpty(val) { + return !isEmpty(val) ? encodeURIComponent(val) : ''; +} + +/** + * get device type + * @returns {string} + */ +function getDeviceType() { + const ua = navigator.userAgent; + if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i + .test(ua.toLowerCase())) { + return '5'; + } + if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i + .test(ua.toLowerCase())) { + return '4'; + } + if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i + .test(ua.toLowerCase())) { + return '3'; + } + return '1'; +} + +/** + * Get floor price + * @param bid {bid} + * @param mediaType {string} + * @returns {Number} + */ +function getFloorPrice(bid, mediaType) { + let floor = 0; + + if (isFn(bid.getFloor)) { + let floorResult = bid.getFloor({ + currency: MAIN_CURRENCY, + mediaType: mediaType, + size: '*' + }); + floor = floorResult.currency === MAIN_CURRENCY && floorResult.floor ? floorResult.floor : 0; + } + + return floor; +} + +/** + * Get the ad sizes array from the bid + * @param bid {bid} + * @param mediaType {string} + * @returns {Array} + */ +function getSizesArray(bid, mediaType) { + let sizes = [] + + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizes = bid.mediaTypes[mediaType].sizes; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizes = bid.sizes; + } + + return sizes; +} + +/** + * Get the preferred user-sync method + * @param filterSettings {filterSettings} + * @param bidderCode {string} + * @returns {string} + */ +function getSyncMethod(filterSettings, bidderCode) { + const iframeConfigs = ['all', 'iframe']; + const pixelConfig = 'image'; + if (filterSettings && iframeConfigs.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return SUPPORTED_SYNC_METHODS.IFRAME; + } + if (!filterSettings || !filterSettings[pixelConfig] || isSyncMethodAllowed(filterSettings[pixelConfig], bidderCode)) { + return SUPPORTED_SYNC_METHODS.PIXEL; + } +} + +/** + * Check sync rule support + * @param filterSetting {Object} + * @param bidderCode {string} + * @returns {boolean} + */ +function isSyncMethodAllowed(filterSetting, bidderCode) { + if (!filterSetting) { + return false; + } + const bidders = isArray(filterSetting.bidders) ? filterSetting.bidders : [bidderCode]; + return filterSetting.filter === 'include' && contains(bidders, bidderCode); +} + +/** + * Get the bidder endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getBidderEndpoint(testMode) { + return testMode ? BIDDER_TEST_ENDPOINT : BIDDER_ENDPOINT; +} + +/** + * Generates the bidder parameters + * @param validBidRequests {Array} + * @param bidderRequest {bidderRequest} + * @returns {Array} + */ +function generateBidParams(validBidRequests, bidderRequest) { + const bidsArray = []; + + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); + } + + return bidsArray; +} + +/** + * Generate bid specific parameters + * @param bid {bid} + * @param bidderRequest {bidderRequest} + * @returns {Object} bid specific params object + */ +function generateBidParameters(bid, bidderRequest) { + const {params} = bid; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + const paramsFloorPrice = isNaN(params.floorPrice) ? 0 : params.floorPrice; + + const bidObject = { + adUnitCode: getBidIdParameter('adUnitCode', bid), + bidId: getBidIdParameter('bidId', bid), + loop: getBidIdParameter('bidderRequestsCount', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + floorPrice: Math.max(getFloorPrice(bid, mediaType), paramsFloorPrice), + mediaType, + sizes: sizesArray, + transactionId: getBidIdParameter('transactionId', bid) + }; + + if (pos) { + bidObject.pos = pos; + } + + if (gpid) { + bidObject.gpid = gpid; + } + + if (placementId) { + bidObject.placementId = placementId; + } + + if (mediaType === VIDEO) { + populateVideoParams(bidObject, bid); + } + + return bidObject; +} + +/** + * Checks if the media type is a banner + * @param bid {bid} + * @returns {boolean} + */ +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; +} + +/** + * Generate params that are common between all bids + * @param sharedParams {sharedParams} + * @param bidderRequest {bidderRequest} + * @returns {object} the common params object + */ +function generateSharedParams(sharedParams, bidderRequest) { + const {bidderCode} = bidderRequest; + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const domain = window.location.hostname; + const generalBidParams = getBidIdParameter('params', sharedParams); + const userIds = getBidIdParameter('userId', sharedParams); + const ortb2Metadata = bidderRequest.ortb2 || {}; + const timeout = config.getConfig('bidderTimeout'); + + const params = { + adapter_version: VERSION, + auction_start: timestamp(), + device_type: getDeviceType(), + dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, + publisher_id: generalBidParams.org, + publisher_name: domain, + session_id: getBidIdParameter('auctionId', sharedParams), + site_domain: domain, + tmax: timeout, + ua: navigator.userAgent, + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$' + }; + + if (syncEnabled) { + const allowedSyncMethod = getSyncMethod(filterSettings, bidderCode); + if (allowedSyncMethod) { + params.cs_method = allowedSyncMethod; + } + } + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + params.gdpr = bidderRequest.gdprConsent.gdprApplies; + params.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (bidderRequest.uspConsent) { + params.us_privacy = bidderRequest.uspConsent; + } + + if (generalBidParams.ifa) { + params.ifa = generalBidParams.ifa; + } + + if (ortb2Metadata.site) { + params.site_metadata = JSON.stringify(ortb2Metadata.site); + } + + if (ortb2Metadata.user) { + params.user_metadata = JSON.stringify(ortb2Metadata.user); + } + + if (bidderRequest && bidderRequest.refererInfo) { + params.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); + params.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); + } + + if (sharedParams.schain) { + params.schain = getSupplyChain(sharedParams.schain); + } + + if (userIds) { + params.userIds = JSON.stringify(userIds); + } + + return params; +} + +/** + * Validates the bidder params + * @param bidRequest {bidRequest} + * @returns {boolean} + */ +function validateParams(bidRequest) { + let isValid = true; + + if (!bidRequest.params) { + logWarn('Kueez adapter - missing params'); + isValid = false; + } + + if (!bidRequest.params.org) { + logWarn('Kueez adapter - org is a required param'); + isValid = false; + } + + return isValid; +} + +/** + * Validates the bidder params + * @param validBidRequests {Array} + * @param sharedParams {sharedParams} + * @param bidderRequest {bidderRequest} + * @returns {Object} + */ +function prepareBids(validBidRequests, sharedParams, bidderRequest) { + return { + params: generateSharedParams(sharedParams, bidderRequest), + bids: generateBidParams(validBidRequests, bidderRequest) + } +} + +function getPlaybackMethod(bid) { + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + return playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + return playbackMethod; + } +} + +function populateVideoParams(params, bid) { + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + const placement = deepAccess(bid, `mediaTypes.video.placement`); + const playbackMethod = getPlaybackMethod(bid); + const skip = deepAccess(bid, `mediaTypes.video.skip`); + + if (linearity) { + params.linearity = linearity; + } + + if (maxDuration) { + params.maxDuration = maxDuration; + } + + if (minDuration) { + params.minDuration = minDuration; + } + + if (placement) { + params.placement = placement; + } + + if (playbackMethod) { + params.playbackMethod = playbackMethod; + } + + if (skip) { + params.skip = skip; + } +} + +/** + * Processes the bid responses + * @param bids {Array} + * @returns {Array} + */ +function parseBidResponses(bids) { + return bids.map(bid => { + const bidResponse = { + cpm: bid.cpm, + creativeId: bid.requestId, + currency: bid.currency || MAIN_CURRENCY, + height: bid.height, + mediaType: bid.mediaType, + meta: { + mediaType: bid.mediaType + }, + netRevenue: bid.netRevenue || true, + nurl: bid.nurl, + requestId: bid.requestId, + ttl: bid.ttl || TTL, + width: bid.width + }; + + if (bid.adomain && bid.adomain.length) { + bidResponse.meta.advertiserDomains = bid.adomain; + } + + if (bid.mediaType === VIDEO) { + bidResponse.vastXml = bid.vastXml; + } else if (bid.mediaType === BANNER) { + bidResponse.ad = bid.ad; + } + + return bidResponse; + }); +} diff --git a/modules/kueezBidAdapter.md b/modules/kueezBidAdapter.md new file mode 100644 index 00000000000..8b17e40f503 --- /dev/null +++ b/modules/kueezBidAdapter.md @@ -0,0 +1,73 @@ +#Overview + +Module Name: Kueez Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: prebid@kueez.com + +# Description + +The Kueez adapter requires setup and approval from the Kueez team. Please reach out to prebid@kueez.com for more information. + +The adapter supports Banner and Video(instream) media types. + +# Bid Parameters + +## Video + +| Name | Scope | Type | Description | Example +|---------------| ----- | ---- |-------------------------------------------------------------------| ------- +| `org` | required | String | the organization Id provided by your Kueez representative | "test-publisher-id" +| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 1.50 +| `placementId` | optional | String | A unique placement identifier | "12345678" +| `testMode` | optional | Boolean | This activates the test mode | false + +# Test Parameters + +```javascript +var adUnits = [{ + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bids: [{ + bidder: 'kueez', + params: { + org: 'test-org-id', // Required + floorPrice: 0.2, // Optional + placementId: '12345678', // Optional + testMode: true // Optional + } + }] +}, + { + code: 'dfp-video-div', + sizes: [ + [640, 480] + ], + mediaTypes: { + video: { + playerSize: [ + [640, 480] + ], + context: 'instream' + } + }, + bids: [{ + bidder: 'kueez', + params: { + org: 'test-org-id', // Required + floorPrice: 1.50, // Optional + placementId: '12345678', // Optional + testMode: true // Optional + } + }] + } +]; +``` diff --git a/modules/kummaBidAdapter.md b/modules/kummaBidAdapter.md deleted file mode 100644 index 639e0c97d08..00000000000 --- a/modules/kummaBidAdapter.md +++ /dev/null @@ -1,87 +0,0 @@ -# Overview - -**Module Name**: Kumma Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: yehonatan@kumma.com - -# Description - -Connects to Kumma demand source to fetch bids. -Banner, Native, Video formats are supported. -Please use ```kumma``` as the bidder code. - -# Test Parameters -``` - var adUnits = [{ - code: 'dfp-native-div', - mediaType: 'native', - mediaTypes: { - native: { - title: { - required: true, - len: 75 - }, - image: { - required: true - }, - body: { - len: 200 - }, - icon: { - required: false - } - } - }, - bids: [{ - bidder: 'kumma', - params: { - pubId: '29521', - siteId: '26047', - placementId: '123', - bidFloor: '0.001', // optional - ifa: 'XXX-XXX', // optional - latitude: '40.712775', // optional - longitude: '-74.005973', // optional - } - }] - }, - { - code: 'dfp-banner-div', - mediaTypes: { - banner: { - sizes: [ - [300, 250] - ], - } - }, - bids: [{ - bidder: 'kumma', - params: { - pubId: '29521', - siteId: '26049', - placementId: '123', - } - }] - }, - { - code: 'dfp-video-div', - sizes: [640, 480], - mediaTypes: { - video: { - context: "instream" - } - }, - bids: [{ - bidder: 'kumma', - params: { - pubId: '29521', - siteId: '26049', - placementId: '123', - video: { - skipppable: true, - } - } - }] - } - ]; -``` diff --git a/modules/lassoBidAdapter.js b/modules/lassoBidAdapter.js new file mode 100644 index 00000000000..da48bc460c7 --- /dev/null +++ b/modules/lassoBidAdapter.js @@ -0,0 +1,133 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'lasso'; +const ENDPOINT_URL = 'https://trc.lhmos.com/prebid'; +const GET_IUD_URL = 'https://secure.adnxs.com/getuid?'; +const COOKIE_NAME = 'aim-xr'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.adUnitId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + if (validBidRequests.length === 0) { + return []; + } + + let aimXR = ''; + if (storage.cookiesAreEnabled) { + aimXR = storage.getCookie(COOKIE_NAME, undefined); + } + + return validBidRequests.map(bidRequest => { + let sizes = [] + if (bidRequest.mediaTypes && bidRequest.mediaTypes[BANNER] && bidRequest.mediaTypes[BANNER].sizes) { + sizes = bidRequest.mediaTypes[BANNER].sizes; + } + + const payload = { + auctionStart: bidderRequest.auctionStart, + url: encodeURIComponent(window.location.href), + bidderRequestId: bidRequest.bidderRequestId, + adUnitCode: bidRequest.adUnitCode, + auctionId: bidRequest.auctionId, + bidId: bidRequest.bidId, + transactionId: bidRequest.transactionId, + device: JSON.stringify(getDeviceData()), + sizes, + aimXR, + uid: '$UID', + params: JSON.stringify(bidRequest.params), + crumbs: JSON.stringify(bidRequest.crumbs), + prebidVersion: '$prebid.version$', + version: 2, + coppa: config.getConfig('coppa') == true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined + } + + return { + method: 'GET', + url: getBidRequestUrl(aimXR), + data: payload, + options: { + withCredentials: true + }, + }; + }); + }, + + interpretResponse: function(serverResponse) { + const response = serverResponse && serverResponse.body; + const bidResponses = []; + + if (!response || !response.bid.ad) { + return bidResponses; + } + + const bidResponse = { + requestId: response.bidid, + cpm: response.bid.price, + currency: response.cur, + width: response.bid.w, + height: response.bid.h, + creativeId: response.bid.crid, + netRevenue: response.netRevenue, + ttl: response.ttl, + ad: response.bid.ad, + mediaType: response.bid.mediaType, + meta: { + secondaryCatIds: response.bid.cat, + advertiserDomains: response.bid.advertiserDomains, + advertiserName: response.meta.advertiserName, + mediaType: response.bid.mediaType + } + }; + bidResponses.push(bidResponse); + return bidResponses; + }, + + onTimeout: function(timeoutData) { + if (timeoutData === null) { + return; + } + ajax(ENDPOINT_URL + '/timeout', null, JSON.stringify(timeoutData), { + method: 'POST', + withCredentials: false + }); + }, + + onBidWon: function(bid) { + ajax(ENDPOINT_URL + '/won', null, JSON.stringify(bid), { + method: 'POST', + withCredentials: false + }); + }, + + supportedMediaTypes: [BANNER] +} + +function getBidRequestUrl(aimXR) { + if (!aimXR) { + return GET_IUD_URL + ENDPOINT_URL + '/request'; + } + return ENDPOINT_URL + '/request' +} + +function getDeviceData() { + const win = window.top; + return { + ua: navigator.userAgent, + width: win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth, + height: win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight, + browserLanguage: navigator.language, + } +} + +registerBidder(spec); diff --git a/modules/lassoBidAdapter.md b/modules/lassoBidAdapter.md new file mode 100644 index 00000000000..43927fe890c --- /dev/null +++ b/modules/lassoBidAdapter.md @@ -0,0 +1,29 @@ +# Overview + +**Module Name**: Lasso Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: headerbidding@lassomarketing.io + +# Description + +Connects to Lasso demand source to fetch bids. +Only banner format supported. + +# Test Parameters + +``` +var adUnits = [{ + code: 'banner-ad-unit', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [{ + bidder: 'lasso', + params: { + adUnitId: '0' + } + }] +}]; +``` diff --git a/modules/lemmaBidAdapter.md b/modules/lemmaBidAdapter.md deleted file mode 100644 index 29e72e028b9..00000000000 --- a/modules/lemmaBidAdapter.md +++ /dev/null @@ -1,66 +0,0 @@ -# Overview - -``` -Module Name: Lemma Bid Adapter -Module Type: Bidder Adapter -Maintainer: lemmadev@lemmatechnologies.com -``` - -# Description - -Connects to Lemma exchange for bids. -Lemma bid adapter supports Video, Banner formats. - -# Sample Banner Ad Unit: For Publishers -``` -var adUnits = [{ - code: 'div-lemma-ad-1', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], // required - } - }, - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'lemma', - params: { - pubId: 1, // required - adunitId: '3768', // required - latitude: 37.3230, - longitude: -122.0322, - device_type: 2, - banner: { - w: 300, - h: 250 - } - } - }] -}]; -``` - -# Sample Video Ad Unit: For Publishers -``` -var adUnits = [{ - mediaType: 'video', - mediaTypes: { - video: { - playerSize: [640, 480], // required - context: 'instream' - } - }, - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'lemma', - params: { - pubId: 1, // required - adunitId: '3769', // required - latitude: 37.3230, - longitude: -122.0322, - device_type: 4, - video: { - mimes: ['video/mp4','video/x-flv'], // required - } - } - }] -}]; -``` diff --git a/modules/lifestreetBidAdapter.js b/modules/lifestreetBidAdapter.js new file mode 100644 index 00000000000..6a8b783ce21 --- /dev/null +++ b/modules/lifestreetBidAdapter.js @@ -0,0 +1,143 @@ +import { isInteger } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'lifestreet'; +const ADAPTER_VERSION = '$prebid.version$'; + +const urlTemplate = template`https://ads.lfstmedia.com/gate/${'adapter'}/${'slot'}?adkey=${'adkey'}&ad_size=${'ad_size'}&__location=${'location'}&__referrer=${'referrer'}&__wn=${'wn'}&__sf=${'sf'}&__fif=${'fif'}&__if=${'if'}&__stamp=${'stamp'}&__pp=1&__hb=1&_prebid_json=1&__gz=1&deferred_format=vast_2_0,vast_3_0&__hbver=${'hbver'}`; + +/** + * A helper function for template to generate string from boolean + */ +function boolToString(value) { + return value ? '1' : '0'; +} + +/** + * A helper function to form URL from the template + */ +function template(strings, ...keys) { + return function(...values) { + let dict = values[values.length - 1] || {}; + let result = [strings[0]]; + keys.forEach(function(key, i) { + let value = isInteger(key) ? values[key] : dict[key]; + result.push(value, strings[i + 1]); + }); + return result.join(''); + }; +} + +/** + * Creates a bid requests for a given bid. + * + * @param {BidRequest} bid The bid params to use for formatting a request + */ +function formatBidRequest(bid, bidderRequest = {}) { + const {params} = bid; + const {referer} = (bidderRequest.refererInfo || {}); + let url = urlTemplate({ + adapter: 'prebid', + slot: params.slot, + adkey: params.adkey, + ad_size: params.ad_size, + location: referer, + referrer: referer, + wn: boolToString(/fb_http/i.test(window.name)), + sf: boolToString(window['sfAPI'] || window['$sf']), + fif: boolToString(window['inDapIF'] === true), + if: boolToString(window !== window.top), + stamp: new Date().getTime(), + hbver: ADAPTER_VERSION + }); + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + const gdpr = '&__gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? '1' : '0'); + url += gdpr; + } + if (bidderRequest.gdprConsent.consentString !== undefined) { + url += `&__consent=${bidderRequest.gdprConsent.consentString}`; + } + } + + // ccpa support + if (bidderRequest.uspConsent) { + url += `&__us_privacy=${bidderRequest.uspConsent}` + } + + return { + method: 'GET', + url: url, + bidId: bid.bidId + }; +} + +function isResponseValid(response) { + return !/^\s*\{\s*"advertisementAvailable"\s*:\s*false/i.test(response.content) && + response.content.indexOf('') === -1 && /* (typeof response.cpm !== 'undefined') && */ + response.status === 1; +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['lsm'], + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid: (bid = {}) => { + const {params = {}} = bid; + return !!(params.slot && params.adkey && params.ad_size); + }, + + buildRequests: (validBidRequests, bidderRequest) => { + return validBidRequests.map(bid => { + return formatBidRequest(bid, bidderRequest) + }); + }, + + interpretResponse: (serverResponse, bidRequest) => { + const bidResponses = []; + let response = serverResponse.body; + if (!isResponseValid(response)) { + return bidResponses; + } + + const isVideo = response.content_type.indexOf('vast') > -1; + const mediaType = isVideo ? VIDEO : BANNER; + + const bidResponse = { + requestId: bidRequest.bidId, + cpm: response.cpm, + currency: response.currency ? response.currency : 'USD', + width: response.width, + height: response.height, + creativeId: response.creativeId, + netRevenue: response.netRevenue ? response.netRevenue : true, + ttl: response.ttl ? response.ttl : 86400, + mediaType, + meta: { + mediaType, + advertiserDomains: response.advertiserDomains + } + }; + + if (response.hasOwnProperty('dealId')) { + bidResponse.dealId = response.dealId; + } + if (isVideo) { + if (typeof response.vastUrl !== 'undefined') { + bidResponse.vastUrl = response.vastUrl; + } else { + bidResponse.vastXml = response.content; + } + } else { + bidResponse.ad = response.content; + } + + bidResponses.push(bidResponse); + return bidResponses; + } +}; + +registerBidder(spec); diff --git a/modules/lifestreetBidAdapter.md b/modules/lifestreetBidAdapter.md deleted file mode 100644 index a874792d84c..00000000000 --- a/modules/lifestreetBidAdapter.md +++ /dev/null @@ -1,76 +0,0 @@ -# Overview - -``` -Module Name: Lifestreet Bid Adapter -Module Type: Lifestreet Adapter -Maintainer: hb.tech@lifestreet.com -``` - -# Description - -Module that connects to Lifestreet's demand sources - -Values, listed in `ALL_BANNER_SIZES` and `ALL_VIDEO_SIZES` are all the values which our server supports. -For `ad_size`, please use one of that values in following format: `ad_size: WIDTHxHEIGHT` - -# Test Parameters -```javascript - const ALL_BANNER_SIZES = [ - [120, 600], [160, 600], [300, 250], [300, 600], [320, 480], - [320, 50], [468, 60], [510, 510], [600, 300], - [720, 300], [728, 90], [760, 740], [768, 1024] - ]; - - const ALL_VIDEO_SIZES = [ - [640, 480], [650, 520], [970, 580] - ] -``` - -# Test Parameters (Banner) -``` - const adUnits = [ - { - code: 'test-ad', - mediaTypes: { - banner: { - sizes: [[160, 600]], - } - }, - bids: [ - { - bidder: 'lifestreet', - params: { - slot: 'slot166704', - adkey: '78c', - ad_size: '160x600' - } - } - ] - }, - ]; -``` - -# Test Parameters (Video) -``` - const adUnits = [ - { - code: 'test-video-ad', - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' - } - }, - bids: [ - { - bidder: 'lifestreet', - params: { - slot: 'slot1227631', - adkey: 'a98', - ad_size: '640x480' - } - } - ] - } - ]; -``` diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index a278a587038..c39fd36e597 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -157,7 +157,8 @@ function buildPlacement(bidRequest) { }), type: bidRequest.params.adUnitType.toUpperCase(), publisherId: bidRequest.params.publisherId, - userIdAsEids: bidRequest.userIdAsEids + userIdAsEids: bidRequest.userIdAsEids, + supplyChain: bidRequest.schain } } } diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index 1116fd99ba0..fe69220e123 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { timestamp, logInfo, getWindowTop } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import { getGlobal } from '../src/prebidGlobal.js'; @@ -67,10 +67,10 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE auc: bidRequest.auc, buc: bidRequest.buc, lw: bidRequest.lw - } + }; logInfo(bidRequest); - }) + }); logInfo(livewrappedAnalyticsAdapter.requestEvents); break; case CONSTANTS.EVENTS.BID_RESPONSE: @@ -183,7 +183,7 @@ livewrappedAnalyticsAdapter.sendEvents = function() { } ajax(initOptions.endpoint || URL, undefined, JSON.stringify(events), {method: 'POST'}); -} +}; function getAdblockerRecovered() { try { diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 8afeaf80652..3839eb80b91 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -4,6 +4,7 @@ import {config} from '../src/config.js'; import {find} from '../src/polyfill.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'livewrapped'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -46,6 +47,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + const userId = find(bidRequests, hasUserId); const pubcid = find(bidRequests, hasPubcid); const publisherId = find(bidRequests, hasPublisherId); @@ -59,7 +63,7 @@ export const spec = { const bundle = find(bidRequests, hasBundleParam); const tid = find(bidRequests, hasTidParam); const schain = bidRequests[0].schain; - let ortb2 = config.getConfig('ortb2'); + let ortb2 = bidderRequest.ortb2; const eids = handleEids(bidRequests); bidUrl = bidUrl ? bidUrl.params.bidUrl : URL; url = url ? url.params.url : (getAppDomain() || getTopWindowLocation(bidderRequest)); @@ -276,8 +280,7 @@ function handleEids(bidRequests) { } function getTopWindowLocation(bidderRequest) { - let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; - return config.getConfig('pageUrl') || url; + return bidderRequest?.refererInfo?.page; } function getAppBundle() { diff --git a/modules/liveyieldAnalyticsAdapter.js b/modules/liveyieldAnalyticsAdapter.js index 411b76a5149..997c0eaace0 100644 --- a/modules/liveyieldAnalyticsAdapter.js +++ b/modules/liveyieldAnalyticsAdapter.js @@ -1,5 +1,5 @@ import { logError } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index e58c643f4f0..1dbe89f5a49 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -37,7 +37,8 @@ export const spec = { const UTC_OFFSET = new Date().getTimezoneOffset(); const UA = navigator.userAgent; const USP = BIDDER_REQUEST.uspConsent || null; - const REFERER = BIDDER_REQUEST.refererInfo ? new URL(BIDDER_REQUEST.refererInfo.referer).hostname : window.location.hostname; + // TODO: does the fallback make sense here? + const REFERER = BIDDER_REQUEST?.refererInfo?.domain || window.location.host; const BIDDER_GDPR = BIDDER_REQUEST.gdprConsent && BIDDER_REQUEST.gdprConsent.gdprApplies ? 1 : null; const BIDDER_GDPRS = BIDDER_REQUEST.gdprConsent && BIDDER_REQUEST.gdprConsent.consentString ? BIDDER_REQUEST.gdprConsent.consentString : null; @@ -73,7 +74,7 @@ export const spec = { us_privacy: USP } } - } + }; if (isSet(DNT)) { requestData.device.dnt = DNT; @@ -93,7 +94,7 @@ export const spec = { id: bid.params.aid, name: bid.params.appname, bundle: bid.params.bundleid - } + }; if (bid.params.contentId) { requestData.app.content = { diff --git a/modules/lockerdomeBidAdapter.js b/modules/lockerdomeBidAdapter.js index 66accb4e02a..5c38753c1e2 100644 --- a/modules/lockerdomeBidAdapter.js +++ b/modules/lockerdomeBidAdapter.js @@ -21,12 +21,11 @@ export const spec = { }; }); - const bidderRequestCanonicalUrl = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.canonicalUrl) || ''; - const bidderRequestReferer = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || ''; const payload = { bidRequests: adUnitBidRequests, - url: encodeURIComponent(bidderRequestCanonicalUrl), - referrer: encodeURIComponent(bidderRequestReferer) + // TODO: are these the right refererInfo values? + url: encodeURIComponent(bidderRequest?.refererInfo?.canonicalUrl || ''), + referrer: encodeURIComponent(bidderRequest?.refererInfo?.topmostLocation || '') }; if (schain) { payload.schain = schain; diff --git a/modules/loganBidAdapter.js b/modules/loganBidAdapter.js index 75327453b2e..7aa82e3046c 100644 --- a/modules/loganBidAdapter.js +++ b/modules/loganBidAdapter.js @@ -2,6 +2,7 @@ import { isFn, deepAccess, getWindowTop } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'logan'; const AD_URL = 'https://USeast2.logan.ai/pbjs'; @@ -50,6 +51,9 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const winTop = getWindowTop(); const location = winTop.location; const placements = []; @@ -68,7 +72,7 @@ export const spec = { request.ccpa = bidderRequest.uspConsent; } if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent + request.gdpr = bidderRequest.gdprConsent; } } @@ -81,7 +85,7 @@ export const spec = { schain: bid.schain || {}, bidfloor: getBidFloor(bid) }; - const mediaType = bid.mediaTypes + const mediaType = bid.mediaTypes; if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { placement.sizes = mediaType[BANNER].sizes; diff --git a/modules/logicadBidAdapter.js b/modules/logicadBidAdapter.js index 2c919f9c157..034450c447f 100644 --- a/modules/logicadBidAdapter.js +++ b/modules/logicadBidAdapter.js @@ -1,5 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'logicad'; const ENDPOINT_URL = 'https://pb.ladsp.com/adrequest/prebid'; @@ -11,6 +12,9 @@ export const spec = { return !!(bid.params && bid.params.tid); }, buildRequests: function (bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + const requests = []; for (let i = 0, len = bidRequests.length; i < len; i++) { const request = { @@ -60,7 +64,8 @@ function newBidRequest(bid, bidderRequest) { mediaTypes: bid.mediaTypes }], prebidJsVersion: '$prebid.version$', - referrer: bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + referrer: bidderRequest.refererInfo.page, auctionStartTime: bidderRequest.auctionStart, eids: bid.userIdAsEids, }; diff --git a/modules/loglyliftBidAdapter.js b/modules/loglyliftBidAdapter.js index dd5f0af1cdf..47f6e54485d 100644 --- a/modules/loglyliftBidAdapter.js +++ b/modules/loglyliftBidAdapter.js @@ -1,6 +1,7 @@ import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'loglylift'; const ENDPOINT_URL = 'https://bid.logly.co.jp/prebid/client/v1'; @@ -14,6 +15,9 @@ export const spec = { }, buildRequests: function (bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + const requests = []; for (let i = 0, len = bidRequests.length; i < len; i++) { const request = { @@ -69,8 +73,8 @@ function newBidRequest(bid, bidderRequest) { params: bid.params, prebidJsVersion: '$prebid.version$', url: window.location.href, - domain: config.getConfig('publisherDomain'), - referer: bidderRequest.refererInfo.referer, + domain: bidderRequest.refererInfo.domain, + referer: bidderRequest.refererInfo.page, auctionStartTime: bidderRequest.auctionStart, currency: currency, timeout: config.getConfig('bidderTimeout') diff --git a/modules/loopmeBidAdapter.md b/modules/loopmeBidAdapter.md deleted file mode 100644 index 1b195a118f2..00000000000 --- a/modules/loopmeBidAdapter.md +++ /dev/null @@ -1,48 +0,0 @@ -# Overview - -``` -Module Name: LoopMe Bid Adapter -Module Type: Bidder Adapter -Maintainer: support@loopme.com -``` - -# Description - -Connect to LoopMe's exchange for bids. - -# Test Parameters (Banner) -``` -var adUnits = [{ - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [{ - bidder: 'loopme', - params: { - ak: 'cc885e3acc' - } - }] -}]; -``` - -# Test Parameters (Video) -``` -var adUnits = [{ - code: 'video1', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream' - } - }, - bids: [{ - bidder: 'loopme', - params: { - ak: '223051e07f' - } - }] -}]; -``` diff --git a/modules/lunamediaBidAdapter.md b/modules/lunamediaBidAdapter.md deleted file mode 100755 index ff5cc86c462..00000000000 --- a/modules/lunamediaBidAdapter.md +++ /dev/null @@ -1,69 +0,0 @@ -# Overview - -``` -Module Name: LunaMedia Bidder Adapter -Module Type: Bidder Adapter -Maintainer: lokesh@advangelists.com -``` - -# Description - -Connects to LunaMedia exchange for bids. - -LunaMedia bid adapter supports Banner and Video ads currently. - -For more informatio - -# Sample Display Ad Unit: For Publishers -```javascript - -var displayAdUnit = [ -{ - code: 'display', - mediaTypes: { - banner: { - sizes: [[300, 250],[320, 50]] - } - } - bids: [{ - bidder: 'lunamedia', - params: { - pubid: '121ab139faf7ac67428a23f1d0a9a71b', - placement: 1234, - size: "320x50" - } - }] -}]; -``` - -# Sample Video Ad Unit: For Publishers -```javascript - -var videoAdUnit = { - code: 'video', - sizes: [320,480], - mediaTypes: { - video: { - playerSize : [[320, 480]], - context: 'instream' - } - }, - bids: [ - { - bidder: 'lunamedia', - params: { - pubid: '121ab139faf7ac67428a23f1d0a9a71b', - placement: 1234, - size: "320x480", - video: { - id: 123, - skip: 1, - mimes : ['video/mp4', 'application/javascript'], - playbackmethod : [2,6], - maxduration: 30 - } - } - } - ] - }; -``` \ No newline at end of file diff --git a/modules/lunamediahbBidAdapter.js b/modules/lunamediahbBidAdapter.js index ebd88d34940..66838014e18 100644 --- a/modules/lunamediahbBidAdapter.js +++ b/modules/lunamediahbBidAdapter.js @@ -2,6 +2,7 @@ import { logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'lunamediahb'; const AD_URL = 'https://balancer.lmgssp.com/?c=o&m=multi'; @@ -33,10 +34,14 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let winTop = window; let location; + // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin try { - location = new URL(bidderRequest.refererInfo.referer) + location = new URL(bidderRequest.refererInfo.page) winTop = window.top; } catch (e) { location = winTop.location; diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 897dc3c8825..835c04ba074 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -176,7 +176,7 @@ export const spec = { responses.forEach(csResp => { if (csResp.body && csResp.body.ext && csResp.body.ext.usersyncs) { try { - let response = csResp.body.ext.usersyncs + let response = csResp.body.ext.usersyncs; let bidders = response.bidder_status; for (let synci in bidders) { let thisSync = bidders[synci]; @@ -314,7 +314,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { }, user: { } - } + }; let bidFloor; if (isFn(bidRequest.getFloor) && !config.getConfig('disableFloors')) { @@ -431,6 +431,10 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { deepSetValue(data, 'source.ext.schain', bidRequest.schain); } + // TODO: getConfig('fpd.context') should not have worked even with legacy FPD support - 'fpd' gets translated + // into 'ortb2' by `setConfig` + // Unclear what the intent was here - maybe `const {context: siteData, user: userData} = getLegacyFpd(config.getConfig('ortb2'))` ? + // (with PB7 `config.getConfig('ortb2')` should be replaced by `bidderRequest.ortb2`) const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); @@ -453,6 +457,8 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); } + // TODO: bidRequest.fpd is not the right place for pbadslot - who's filling that in, if anyone? + // is this meant to be bidRequest.ortb2Imp.ext.data.pbadslot? const pbAdSlot = deepAccess(bidRequest, 'fpd.context.pbAdSlot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); @@ -494,11 +500,12 @@ function _getDigiTrustQueryParams(bidRequest = {}, endpointName) { } function _getPageUrl(bidRequest, bidderRequest) { - let pageUrl = config.getConfig('pageUrl'); + // TODO: do the fallbacks make sense here? + let pageUrl = bidderRequest.refererInfo.page; if (bidRequest.params.referrer) { pageUrl = bidRequest.params.referrer; } else if (!pageUrl) { - pageUrl = bidderRequest.refererInfo.referer; + pageUrl = bidderRequest.refererInfo.topmostLocation; } return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; } @@ -559,7 +566,7 @@ function parseSizes(bid, mediaType) { } else if (typeof deepAccess(bid, 'mediaTypes.banner.sizes') !== 'undefined') { sizes = mapSizes(bid.mediaTypes.banner.sizes); } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizes = mapSizes(bid.sizes) + sizes = mapSizes(bid.sizes); } else { logWarn('LuponMedia: no sizes are setup or found'); } diff --git a/modules/mabidderBidAdapter.js b/modules/mabidderBidAdapter.js new file mode 100644 index 00000000000..12da791d2c4 --- /dev/null +++ b/modules/mabidderBidAdapter.js @@ -0,0 +1,60 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +const BIDDER_CODE = 'mabidder'; +export const baseUrl = 'https://prebid.ecdrsvc.com/bid'; +export const spec = { + supportedMediaTypes: [BANNER], + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + if (typeof bid.params === 'undefined') { + return false; + } + return !!(bid.params.ppid && bid.sizes && Array.isArray(bid.sizes) && Array.isArray(bid.sizes[0])) + }, + buildRequests: function(validBidRequests, bidderRequest) { + const fpd = bidderRequest.ortb2; + const bids = []; + validBidRequests.forEach(bidRequest => { + const sizes = []; + bidRequest.sizes.forEach(size => { + sizes.push({ + width: size[0], + height: size[1] + }); + }); + bids.push({ + bidId: bidRequest.bidId, + sizes: sizes, + ppid: bidRequest.params.ppid, + mediaType: BANNER + }) + }); + const req = { + url: baseUrl, + method: 'POST', + data: { + v: $$PREBID_GLOBAL$$.version, + bids: bids, + url: bidderRequest.refererInfo.page || '', + referer: bidderRequest.refererInfo.ref || '', + fpd: fpd || {} + } + }; + + return req; + }, + interpretResponse: function(serverResponse, request) { + const bidResponses = []; + if (serverResponse.body) { + const body = serverResponse.body; + if (!body || typeof body !== 'object' || !body.Responses || !(body.Responses.length > 0)) { + return []; + } + body.Responses.forEach((bidResponse) => { + bidResponses.push(bidResponse); + }); + } + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/mabidderBidAdapter.md b/modules/mabidderBidAdapter.md new file mode 100644 index 00000000000..ee57cb8cab2 --- /dev/null +++ b/modules/mabidderBidAdapter.md @@ -0,0 +1,31 @@ +#Overview + +``` +Module Name: mabidder Bid Adapter +Module Type: Bidder Adapter +Maintainer: lmprebidadapter@loblaw.ca +``` + +# Description + +Module that connects to MediaAisle demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'test_banner', + mediaTypes: { + banner: { + sizes: [300, 250] + } + }, + bids: [{ + bidder: 'mabidder', + params: { + ppid: 'test' + } + }], + } +]; +``` diff --git a/modules/malltvAnalyticsAdapter.js b/modules/malltvAnalyticsAdapter.js index a0e2a208bc9..af903795e49 100644 --- a/modules/malltvAnalyticsAdapter.js +++ b/modules/malltvAnalyticsAdapter.js @@ -1,5 +1,5 @@ import {ajax} from '../src/ajax.js' -import adapter from '../src/AnalyticsAdapter.js' +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js' import CONSTANTS from '../src/constants.json' import adapterManager from '../src/adapterManager.js' import {getGlobal} from '../src/prebidGlobal.js' diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js index 53f745d4004..cee8e888986 100644 --- a/modules/malltvBidAdapter.js +++ b/modules/malltvBidAdapter.js @@ -47,7 +47,8 @@ export const spec = { if (!propertyId) { propertyId = bidRequest.params.propertyId; } if (!pageViewGuid && bidRequest.params) { pageViewGuid = bidRequest.params.pageViewGuid || ''; } if (!bidderRequestId) { bidderRequestId = bidRequest.bidderRequestId; } - if (!url && bidderRequest) { url = bidderRequest.refererInfo.referer; } + // TODO: is 'page' the right value here? + if (!url && bidderRequest) { url = bidderRequest.refererInfo.page; } if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } if (Object.keys(data).length === 0 && bidRequest.params.data && Object.keys(bidRequest.params.data).length !== 0) { data = bidRequest.params.data; } if (bidderRequest && bidRequest.gdprConsent) { gdrpApplies = bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : true; } @@ -80,7 +81,7 @@ export const spec = { data: data, gdpr_applies: gdrpApplies, gdpr_consent: gdprConsent, - } + }; return [{ method: 'POST', @@ -119,7 +120,7 @@ export const spec = { } return bidResponses; } -} +}; /** * Generate size param for bid request using sizes array diff --git a/modules/marsmediaAnalyticsAdapter.js b/modules/marsmediaAnalyticsAdapter.js index 12c333631a2..c86cc4dfbc2 100644 --- a/modules/marsmediaAnalyticsAdapter.js +++ b/modules/marsmediaAnalyticsAdapter.js @@ -1,5 +1,5 @@ import {ajax} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; /**** diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js index 92374b748c7..82a25af60d1 100644 --- a/modules/marsmediaBidAdapter.js +++ b/modules/marsmediaBidAdapter.js @@ -31,6 +31,7 @@ function MarsmediaAdapter() { var isSecure = 0; if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.stack.length) { // clever trick to get the protocol + // TODO: this should probably use parseUrl var el = document.createElement('a'); el.href = bidderRequest.refererInfo.stack[0]; isSecure = (el.protocol == 'https:') ? 1 : 0; @@ -68,12 +69,15 @@ function MarsmediaAdapter() { } if (bidderRequest && bidderRequest.refererInfo) { var ri = bidderRequest.refererInfo; - site.ref = ri.referer; + // TODO: is 'ref' the right value here? + site.ref = ri.ref; if (ri.stack.length) { site.page = ri.stack[ri.stack.length - 1]; // clever trick to get the domain + // TODO: does this logic make sense? why should domain be set to the lowermost frame's? + // TODO: this should probably use parseUrl var el = document.createElement('a'); el.href = ri.stack[0]; site.domain = el.hostname; diff --git a/modules/mathildeadsBidAdapter.js b/modules/mathildeadsBidAdapter.js index 3f5d94f0df2..6b886b72955 100644 --- a/modules/mathildeadsBidAdapter.js +++ b/modules/mathildeadsBidAdapter.js @@ -2,6 +2,7 @@ import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'mathildeads'; const AD_URL = 'https://endpoint2.mathilde-ads.com/pbjs'; @@ -111,6 +112,9 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let deviceWidth = 0; let deviceHeight = 0; @@ -125,7 +129,7 @@ export const spec = { winLocation = window.location; } - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + const refferUrl = bidderRequest?.refererInfo?.page; let refferLocation; try { refferLocation = refferUrl && new URL(refferUrl); @@ -133,6 +137,7 @@ export const spec = { logMessage(e); } + // TODO: does the fallback make sense here? let location = refferLocation || winLocation; const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; const host = location.host; diff --git a/modules/meazyBidAdapter.md b/modules/meazyBidAdapter.md deleted file mode 100644 index 354673bf590..00000000000 --- a/modules/meazyBidAdapter.md +++ /dev/null @@ -1,23 +0,0 @@ -# Overview - -Module Name: Meazy Bidder Adapter -Module Type: Bidder Adapter -Maintainer: dima@meazy.co - -# Description - -Module that connects to Meazy demand sources - -# Test Parameters -``` -var adUnits = [{ - code: 'test-div', - sizes: [[300, 250]], - bids: [{ - bidder: "meazy", - params: { - pid: '6910b7344ae566a1' - } - }] -}]; -``` \ No newline at end of file diff --git a/modules/mediaforceBidAdapter.js b/modules/mediaforceBidAdapter.js index c686a2e378d..744cf89d655 100644 --- a/modules/mediaforceBidAdapter.js +++ b/modules/mediaforceBidAdapter.js @@ -1,6 +1,7 @@ import { getDNT, deepAccess, isStr, replaceAuctionPrice, triggerPixel, parseGPTSingleSizeArrayToRtbSize, isEmpty } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'mediaforce'; const ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid'; @@ -109,11 +110,15 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + if (validBidRequests.length === 0) { return; } - const referer = bidderRequest && bidderRequest.refererInfo ? encodeURIComponent(bidderRequest.refererInfo.referer) : ''; + // TODO: is 'ref' the right value here? + const referer = bidderRequest && bidderRequest.refererInfo ? encodeURIComponent(bidderRequest.refererInfo.ref) : ''; const auctionId = bidderRequest && bidderRequest.auctionId; const timeout = bidderRequest && bidderRequest.timeout; const dnt = getDNT() ? 1 : 0; @@ -156,6 +161,7 @@ export const spec = { request = { id: Math.round(Math.random() * 1e16).toString(16), site: { + // TODO: this should probably look at refererInfo page: window.location.href, ref: referer, id: bid.params.publisher_id, diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index b77c965802e..e61c2e65c39 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -8,6 +8,8 @@ import {find, includes} from '../src/polyfill.js'; import { OUTSTREAM, INSTREAM } from '../src/video.js'; import { getStorageManager } from '../src/storageManager.js'; import { bidderSettings } from '../src/bidderSettings.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'mediafuse'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -84,6 +86,8 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); const tags = bidRequests.map(bidToTag); const userObjBid = find(bidRequests, hasUserInfo); let userObj = {}; @@ -173,7 +177,7 @@ export const spec = { payload['iab_support'] = { omidpn: 'Mediafuse', omidpv: '$prebid.version$' - } + }; } if (member > 0) { @@ -181,7 +185,7 @@ export const spec = { } if (appDeviceObjBid) { - payload.device = appDeviceObj + payload.device = appDeviceObj; } if (appIdObjBid) { payload.app = appIdObj; @@ -223,16 +227,17 @@ export const spec = { } if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent + payload.us_privacy = bidderRequest.uspConsent; } if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { - rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + // TODO: this collects everything it finds, except for canonicalUrl + rd_ref: encodeURIComponent(bidderRequest.refererInfo.topmostLocation), rd_top: bidderRequest.refererInfo.reachedTop, rd_ifs: bidderRequest.refererInfo.numIframes, rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - } + }; payload.referrer_detection = refererinfo; } @@ -249,7 +254,6 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; - addUserId(eids, deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); @@ -382,7 +386,7 @@ export const spec = { reloadViewabilityScriptWithCorrectParameters(bid); } } -} +}; function isPopulatedArray(arr) { return !!(isArray(arr) && arr.length > 0); @@ -400,7 +404,7 @@ function reloadViewabilityScriptWithCorrectParameters(bid) { if (viewJsPayload) { let prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; - let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload) + let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload); let newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); @@ -479,16 +483,6 @@ function getViewabilityScriptUrlFromPayload(viewJsPayload) { return jsTrackerSrc; } -function hasPurpose1Consent(bidderRequest) { - let result = true; - if (bidderRequest && bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { - result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); - } - } - return result; -} - function formatRequest(payload, bidderRequest) { let request = []; let options = { @@ -497,14 +491,14 @@ function formatRequest(payload, bidderRequest) { let endpointUrl = URL; - if (!hasPurpose1Consent(bidderRequest)) { + if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { endpointUrl = URL_SIMPLE; } if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { options.customHeaders = { 'X-Is-Test': 1 - } + }; } if (payload.tags.length > MAX_IMPS_PER_REQUEST) { @@ -739,7 +733,7 @@ function bidToTag(bid) { tag.code = bid.params.invCode; } tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.use_pmt_rule = bid.params.usePaymentRule || false; tag.prebid = true; tag.disable_psa = true; let bidFloor = getBidFloor(bid); diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js new file mode 100644 index 00000000000..28ca4e0bc50 --- /dev/null +++ b/modules/mediagoBidAdapter.js @@ -0,0 +1,462 @@ +/** + * gulp serve --modules=mediagoBidAdapter,pubCommonId --nolint --notest + */ + +import * as utils from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +// import { config } from '../src/config.js'; +// import { isPubcidEnabled } from './pubCommonId.js'; + +const BIDDER_CODE = 'mediago'; +// const PROTOCOL = window.document.location.protocol; +const ENDPOINT_URL = + // ((PROTOCOL === 'https:') ? 'https' : 'http') + + 'https://rtb-us.mediago.io/api/bid?tn='; +const TIME_TO_LIVE = 500; +// const ENDPOINT_URL = '/api/bid?tn='; +const storage = getStorageManager(); +let globals = {}; +let itemMaps = {}; + +/** + * 获取随机id + * @param {number} a random number from 0 to 15 + * @return {string} random number or random string + */ +// function getRandomId( +// a // placeholder +// ) { +// // if the placeholder was passed, return +// // a random number from 0 to 15 +// return a +// ? ( +// a ^ // unless b is 8, +// ((Math.random() * // in which case +// 16) >> // a random number from +// (a / 4)) +// ) // 8 to 11 +// .toString(16) // in hexadecimal +// : ( // or otherwise a concatenated string: +// [1e7] + // 10000000 + +// 1e3 + // -1000 + +// 4e3 + // -4000 + +// 8e3 + // -80000000 + +// 1e11 +// ) // -100000000000, +// .replace( +// // replacing +// /[018]/g, // zeroes, ones, and eights with +// getRandomId // random hex digits +// ); +// } + +/* ----- mguid:start ------ */ +const COOKIE_KEY_MGUID = '__mguid_'; + +/** + * 获取用户id + * @return {string} + */ +const getUserID = () => { + const i = storage.getCookie(COOKIE_KEY_MGUID); + + if (i === null) { + const uuid = utils.generateUUID(); + storage.setCookie(COOKIE_KEY_MGUID, uuid); + return uuid; + } + return i; +}; + +/* ----- mguid:end ------ */ + +/** + * 获取一个对象的某个值,如果没有则返回空字符串 + * @param {Object} obj 对象 + * @param {...string} keys 键名 + * @return {any} + */ +function getProperty(obj, ...keys) { + let o = obj; + + for (let key of keys) { + // console.log(key, o); + if (o && o[key]) { + o = o[key]; + } else { + return ''; + } + } + return o; +} + +/** + * 是不是移动设备或者平板 + * @return {boolean} + */ +function isMobileAndTablet() { + let check = false; + (function (a) { + let reg1 = new RegExp( + [ + '(android|bbd+|meego)', + '.+mobile|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)', + '|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone', + '|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up.(browser|link)|vodafone|wap', + '|windows ce|xda|xiino|android|ipad|playbook|silk', + ].join(''), + 'i' + ); + let reg2 = new RegExp( + [ + '1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)', + '|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )', + '|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55/|capi|ccwa|cdm-|cell', + '|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)', + '|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene', + '|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c', + '|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|/)|ibro|idea|ig01|ikom', + '|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |/)|klon|kpt |kwc-|kyo(c|k)', + '|le(no|xi)|lg( g|/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50/|ma(te|ui|xo)|mc(01|21|ca)', + '|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]', + '|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)', + '|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio', + '|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55/|sa(ge|ma|mm|ms', + '|ny|va)|sc(01|h-|oo|p-)|sdk/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al', + '|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)', + '|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(.b|g1|si)|utst|', + 'v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)', + '|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-', + '|your|zeto|zte-', + ].join(''), + 'i' + ); + if (reg1.test(a) || reg2.test(a.substr(0, 4))) { + check = true; + } + })(navigator.userAgent || navigator.vendor || window.opera); + return check; +} + +/** + * 获取底价 + * @param {*} bid + * @param {*} mediaType + * @param {*} sizes + * @returns + */ +// function getBidFloor(bid, mediaType, sizes) { +// var floor; +// var size = sizes.length === 1 ? sizes[0] : "*"; +// if (typeof bid.getFloor === "function") { +// const floorInfo = bid.getFloor({ currency: "USD", mediaType, size }); +// if ( +// typeof floorInfo === "object" && +// floorInfo.currency === "USD" && +// !isNaN(parseFloat(floorInfo.floor)) +// ) { +// floor = parseFloat(floorInfo.floor); +// } +// } +// return floor; +// } +function getBidFloor(bid) { + if (!utils.isFn(bid.getFloor)) { + return utils.deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0; + } +} + +/** + * 将尺寸转为RTB识别的尺寸 + * + * @param {Array|Object} requestSizes 配置尺寸 + * @return {Object} + */ +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if ( + utils.isArray(requestSizes) && + requestSizes.length === 2 && + !utils.isArray(requestSizes[0]) + ) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +// 支持的广告尺寸 +const mediagoAdSize = [ + { w: 300, h: 250 }, + { w: 300, h: 600 }, + { w: 728, h: 90 }, + { w: 970, h: 250 }, + { w: 320, h: 50 }, + { w: 160, h: 600 }, + { w: 320, h: 180 }, + { w: 320, h: 100 }, + { w: 336, h: 280 }, +]; + +/** + * 获取广告位配置 + * @param {Array} validBidRequests an an array of bids + * @param {Object} bidderRequest The master bidRequest object + * @return {Object} + */ +function getItems(validBidRequests, bidderRequest) { + let items = []; + items = validBidRequests.map((req, i) => { + let ret = {}; + let mediaTypes = getProperty(req, 'mediaTypes'); + + let sizes = transformSizes(getProperty(req, 'sizes')); + let matchSize; + + // 确认尺寸是否符合我们要求 + for (let size of sizes) { + matchSize = mediagoAdSize.find( + (item) => size.width === item.w && size.height === item.h + ); + if (matchSize) { + break; + } + } + if (!matchSize) { + return {}; + } + + const bidFloor = getBidFloor(req); + // const gpid = + // utils.deepAccess(req, 'ortb2Imp.ext.gpid') || + // utils.deepAccess(req, 'ortb2Imp.ext.data.pbadslot') || + // utils.deepAccess(req, 'params.placementId', 0); + // console.log("wjh getItems:", req, bidFloor, gpid); + + // if (mediaTypes.native) {} + // banner广告类型 + if (mediaTypes.banner) { + let id = '' + (i + 1); + ret = { + id: id, + bidfloor: bidFloor, + banner: { + h: matchSize.h, + w: matchSize.w, + pos: 1, + }, + ext: { + // gpid: gpid, // 加入后无法返回广告 + }, + }; + itemMaps[id] = { + req, + ret, + }; + } + + return ret; + }); + return items; +} + +/** + * 获取rtb请求参数 + * + * @param {Array} validBidRequests an an array of bids + * @param {Object} bidderRequest The master bidRequest object + * @return {Object} + */ +function getParam(validBidRequests, bidderRequest) { + const pubcid = utils.deepAccess(validBidRequests[0], 'crumbs.pubcid'); + // console.log('wjh getParam', validBidRequests, bidderRequest); + let isMobile = isMobileAndTablet() ? 1 : 0; + let isTest = 0; + let auctionId = getProperty(bidderRequest, 'auctionId'); + let items = getItems(validBidRequests, bidderRequest); + + const domain = document.domain; + const location = utils.deepAccess(bidderRequest, 'refererInfo.referer'); + + const timeout = bidderRequest.timeout || 2000; + + if (items && items.length) { + let c = { + id: 'mgprebidjs_' + auctionId, + test: +isTest, + at: 1, + cur: ['USD'], + device: { + connectiontype: 0, + // ip: '64.188.178.115', + js: 1, + // language: "en", + // os: "Microsoft Windows", + // ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19043", + os: navigator.platform || '', + ua: navigator.userAgent, + language: /en/.test(navigator.language) ? 'en' : navigator.language, + }, + ext: {}, + user: { + buyeruid: getUserID(), + id: pubcid, + }, + site: { + name: domain, + domain: domain, + page: location, + ref: location, + mobile: isMobile, + cat: [], // todo + publisher: { + // todo + id: domain, + name: domain, + }, + }, + imp: items, + tmax: timeout, + }; + return c; + } else { + return null; + } +} + +export const spec = { + code: BIDDER_CODE, + // aliases: ['ex'], // short code + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + // console.log('mediago', { + // bid + // }); + if (bid.params.token) { + globals['token'] = bid.params.token; + } + return !!bid.params.token; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {Array} validBidRequests an an array of bids + * @param {Object} bidderRequest The master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + let payload = getParam(validBidRequests, bidderRequest); + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL + globals['token'], + data: payloadString, + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + const bids = getProperty(serverResponse, 'body', 'seatbid', 0, 'bid'); + const cur = getProperty(serverResponse, 'body', 'cur'); + + const bidResponses = []; + for (let bid of bids) { + let impid = getProperty(bid, 'impid'); + if (itemMaps[impid]) { + let bidId = getProperty(itemMaps[impid], 'req', 'bidId'); + const bidResponse = { + requestId: bidId, + cpm: getProperty(bid, 'price'), + width: getProperty(bid, 'w'), + height: getProperty(bid, 'h'), + creativeId: getProperty(bid, 'crid'), + dealId: '', + currency: cur, + netRevenue: true, + ttl: TIME_TO_LIVE, + // referrer: REFERER, + ad: getProperty(bid, 'adm'), + nurl: getProperty(bid, 'nurl'), + // adserverTargeting: { + // granularityMultiplier: 0.1, + // priceGranularity: "pbHg", + // pbMg: "0.01", + // }, + // pbMg: "0.01", + // granularityMultiplier: 0.1, + // priceGranularity: "pbHg", + }; + bidResponses.push(bidResponse); + } + } + + return bidResponses; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + // onTimeout: function (data) { + // // console.log('onTimeout', data); + // // Bidder specifc code + // }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function (bid) { + // console.log('onBidWon: ', bid, config.getConfig('priceGranularity')); + // Bidder specific code + if (bid['nurl']) { + utils.triggerPixel(bid['nurl']); + } + }, + + /** + * Register bidder specific code, which will execute when the adserver targeting has been set for a bid from this bidder + * @param {Bid} The bid of which the targeting has been set + */ +// onSetTargeting: function (bid) { +// // console.log('onSetTargeting', bid); +// // Bidder specific code +// }, +}; +registerBidder(spec); diff --git a/modules/mediagoBidAdapter.md b/modules/mediagoBidAdapter.md index 87c38f662a3..464ea59f11c 100644 --- a/modules/mediagoBidAdapter.md +++ b/modules/mediagoBidAdapter.md @@ -11,6 +11,7 @@ Maintainer: fangsimin@baidu.com Module that connects to MediaGo's demand sources # Test Parameters + ``` var adUnits = [ { diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index 5eb32a3f6e4..8c2b2c32e1c 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -5,7 +5,6 @@ import { deepClone, deepSetValue, getDNT, - getWindowTop, inIframe, isArray, isEmpty, @@ -16,13 +15,13 @@ import { logError, logWarn, mergeDeep, - parseUrl, triggerPixel } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {createEidsArray} from './userId/eids.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const AUCTION_TYPE = 1; const BIDDER_CODE = 'mediakeys'; @@ -85,19 +84,6 @@ const ORTB_VIDEO_PARAMS = { api: value => Array.isArray(value) && value.every(v => [1, 2, 3, 4, 5, 6].indexOf(v) !== -1), }; -/** - * Detects the capability to reach window.top. - * - * @returns {boolean} - */ -function canAccessTopWindow() { - try { - return !!getWindowTop().location.href; - } catch (error) { - return false; - } -} - /** * Returns the OpenRtb deviceType id detected from User Agent * Voluntary limited to phone, tablet, desktop. @@ -434,7 +420,7 @@ function createVideoImp(bid) { } }); - return video + return video; } /** @@ -611,6 +597,9 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const payload = createOrtbTemplate(); // Pass the auctionId as ortb2 id @@ -650,21 +639,18 @@ export const spec = { // Assign payload.site from refererinfo if (bidderRequest.refererInfo) { + // TODO: reachedTop is probably not the right check here - it may be false when page is available or vice-versa if (bidderRequest.refererInfo.reachedTop) { - const sitePage = bidderRequest.refererInfo.referer; - deepSetValue(payload, 'site.page', sitePage); - deepSetValue(payload, 'site.domain', parseUrl(sitePage, { - noDecodeWholeURL: true - }).hostname); - - if (canAccessTopWindow()) { - deepSetValue(payload, 'site.ref', getWindowTop().document.referrer); + deepSetValue(payload, 'site.page', bidderRequest.refererInfo.page); + deepSetValue(payload, 'site.domain', bidderRequest.refererInfo.domain) + if (bidderRequest.refererInfo.ref) { + deepSetValue(payload, 'site.ref', bidderRequest.refererInfo.ref); } } } // Handle First Party Data (need publisher fpd setup) - const fpd = config.getConfig('ortb2') || {}; + const fpd = bidderRequest.ortb2 || {}; if (fpd.site) { mergeDeep(payload, { site: fpd.site }); } @@ -717,7 +703,7 @@ export const spec = { agencyName: deepAccess(bid, 'ext.agency_name', null), primaryCatId: getPrimaryCatFromResponse(bid.cat), mediaType - } + }; const newBid = { requestId: bid.impid, diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 09ebbc9bc31..b7abe5f56cf 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -11,7 +11,7 @@ import { uniques, getHighestCpm } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import {ajax} from '../src/ajax.js'; @@ -182,16 +182,16 @@ class Configure { class PageDetail { constructor () { - const canonicalUrl = this._getUrlFromSelector('link[rel="canonical"]', 'href'); const ogUrl = this._getUrlFromSelector('meta[property="og:url"]', 'content'); const twitterUrl = this._getUrlFromSelector('meta[name="twitter:url"]', 'content'); const refererInfo = getRefererInfo(); - this.domain = URL.parseUrl(refererInfo.referer).hostname; - this.page = refererInfo.referer; + // TODO: are these the right refererInfo values? + this.domain = refererInfo.domain; + this.page = refererInfo.page; this.is_top = refererInfo.reachedTop; - this.referrer = this._getTopWindowReferrer(); - this.canonical_url = canonicalUrl; + this.referrer = refererInfo.ref || window.document.referrer; + this.canonical_url = refererInfo.canonicalUrl; this.og_url = ogUrl; this.twitter_url = twitterUrl; this.screen = this._getWindowSize(); diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index eb3f8b7416a..d19862d2f64 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -1,9 +1,22 @@ -import { parseUrl, getWindowTop, isArray, getGptSlotInfoForAdUnitCode, isStr, deepAccess, isEmpty, logError, triggerPixel, buildUrl, isEmptyStr, logInfo } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { Renderer } from '../src/Renderer.js'; +import { + buildUrl, + deepAccess, + getGptSlotInfoForAdUnitCode, + getWindowTop, + isArray, + isEmpty, + isEmptyStr, + isStr, + logError, + logInfo, + triggerPixel +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {Renderer} from '../src/Renderer.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'medianet'; const BID_URL = 'https://prebid.media.net/rtb/prebid'; @@ -19,6 +32,8 @@ const EVENTS = { }; const EVENT_PIXEL_URL = 'qsearch-a.akamaihd.net/log'; const OUTSTREAM = 'outstream'; + +// TODO: this should be picked from bidderRequest let refererInfo = getRefererInfo(); let mnData = {}; @@ -27,8 +42,8 @@ window.mnet = window.mnet || {}; window.mnet.queue = window.mnet.queue || []; mnData.urlData = { - domain: parseUrl(refererInfo.referer, {noDecodeWholeURL: true}).hostname, - page: refererInfo.referer, + domain: refererInfo.domain, + page: refererInfo.page, isTop: refererInfo.reachedTop }; @@ -42,13 +57,15 @@ function getTopWindowReferrer() { } } -function siteDetails(site) { +function siteDetails(site, bidderRequest) { + const urlData = bidderRequest.refererInfo; site = site || {}; let siteData = { - domain: site.domain || mnData.urlData.domain, - page: site.page || mnData.urlData.page, + domain: site.domain || urlData.domain, + page: site.page || urlData.page, ref: site.ref || getTopWindowReferrer(), - isTop: site.isTop || mnData.urlData.isTop + topMostLocation: urlData.topmostLocation, + isTop: site.isTop || urlData.reachedTop }; return Object.assign(siteData, getPageMeta()); @@ -173,6 +190,7 @@ function slotParams(bidRequest) { // check with Media.net Account manager for bid floor and crid parameters let params = { id: bidRequest.bidId, + transactionId: bidRequest.transactionId, ext: { dfp_id: bidRequest.adUnitCode, display_count: bidRequest.bidRequestsCount @@ -306,10 +324,11 @@ function getBidderURL(cid) { function generatePayload(bidRequests, bidderRequests) { return { - site: siteDetails(bidRequests[0].params.site), + site: siteDetails(bidRequests[0].params.site, bidderRequests), ext: extParams(bidRequests[0], bidderRequests), id: bidRequests[0].auctionId, imp: bidRequests.map(request => slotParams(request)), + ortb2: bidderRequests.ortb2, tmax: bidderRequests.timeout || config.getConfig('bidderTimeout') } } @@ -432,6 +451,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(bidRequests, bidderRequests) { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + let payload = generatePayload(bidRequests, bidderRequests); return { method: 'POST', diff --git a/modules/mediasniperBidAdapter.js b/modules/mediasniperBidAdapter.js index 3e57503f7fb..417642b7a6f 100644 --- a/modules/mediasniperBidAdapter.js +++ b/modules/mediasniperBidAdapter.js @@ -2,23 +2,21 @@ import { deepAccess, deepClone, deepSetValue, - getWindowTop, + getBidIdParameter, inIframe, isArray, isEmpty, isFn, isNumber, isStr, - logWarn, logError, logMessage, - parseUrl, - getBidIdParameter, + logWarn, triggerPixel, } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; const BIDDER_CODE = 'mediasniper'; const DEFAULT_BID_TTL = 360; @@ -76,19 +74,18 @@ export const spec = { // Assign payload.site from refererinfo if (bidderRequest.refererInfo) { + // TODO: reachedTop is probably not the right check - it may be false when page is available or vice-versa if (bidderRequest.refererInfo.reachedTop) { - const sitePage = bidderRequest.refererInfo.referer; + const sitePage = bidderRequest.refererInfo.page; deepSetValue(payload, 'site.page', sitePage); deepSetValue( payload, 'site.domain', - parseUrl(sitePage, { - noDecodeWholeURL: true, - }).hostname + bidderRequest.refererInfo.domain ); - if (canAccessTopWindow()) { - deepSetValue(payload, 'site.ref', getWindowTop().document.referrer); + if (bidderRequest.refererInfo?.ref) { + deepSetValue(payload, 'site.ref', bidderRequest.refererInfo.ref); } } } @@ -165,19 +162,6 @@ export const spec = { }; registerBidder(spec); -/** - * Detects the capability to reach window.top. - * - * @returns {boolean} - */ -function canAccessTopWindow() { - try { - return !!getWindowTop().location.href; - } catch (error) { - return false; - } -} - /** * Returns an openRTB 2.5 object. * This one will be populated at each step of the buildRequest process. diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 427a16f1341..a6903830b80 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -2,6 +2,7 @@ import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' @@ -30,6 +31,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let codes = []; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; let floor = {}; @@ -55,7 +59,8 @@ export const spec = { }); const payload = { codes: codes, - referer: encodeURIComponent(bidderRequest.refererInfo.referer), + // TODO: is 'page' the right value here? + referer: encodeURIComponent(bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation), pbjs: '$prebid.version$' }; if (bidderRequest) { // modules informations (gdpr, ccpa, schain, userId) diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js index 352c2d074e8..6a10c2a94eb 100644 --- a/modules/merkleIdSystem.js +++ b/modules/merkleIdSystem.js @@ -11,7 +11,7 @@ import {submodule} from '../src/hook.js' import {getStorageManager} from '../src/storageManager.js'; const MODULE_NAME = 'merkleId'; -const ID_URL = 'https://id2.sv.rkdms.com/identity/'; +const ID_URL = 'https://prebid.sv.rkdms.com/identity/'; const DEFAULT_REFRESH = 7 * 3600; const SESSION_COOKIE_NAME = '_svsid'; @@ -30,19 +30,19 @@ function getSession(configParams) { function setCookie(name, value, expires) { let expTime = new Date(); expTime.setTime(expTime.getTime() + expires * 1000 * 60); - storage.setCookie(name, value, expTime.toUTCString()); + storage.setCookie(name, value, expTime.toUTCString(), 'Lax'); } function setSession(storage, response) { - logInfo('Merkle setting session '); - if (response && response.c && response.c.value && typeof response.c.value === 'string') { - setCookie(SESSION_COOKIE_NAME, response.c.value, storage.expires); + logInfo('Merkle setting ' + `${SESSION_COOKIE_NAME}`); + if (response && response[SESSION_COOKIE_NAME] && typeof response[SESSION_COOKIE_NAME] === 'string') { + setCookie(SESSION_COOKIE_NAME, response[SESSION_COOKIE_NAME], storage.expires); } } function constructUrl(configParams) { const session = getSession(configParams); - let url = configParams.endpoint + `?vendor=${configParams.vendor}&sv_cid=${configParams.sv_cid}&sv_domain=${configParams.sv_domain}&sv_pubid=${configParams.sv_pubid}`; + let url = configParams.endpoint + `?sv_domain=${configParams.sv_domain}&sv_pubid=${configParams.sv_pubid}&ssp_ids=${configParams.ssp_ids.join()}`; if (session) { url = `${url}&sv_session=${session}`; } @@ -86,45 +86,52 @@ function generateId(configParams, configStorage) { /** @type {Submodule} */ export const merkleIdSubmodule = { /** - * used to link submodule with config - * @type {string} - */ + * used to link submodule with config + * @type {string} + */ name: MODULE_NAME, + /** - * decode the stored id value for passing to bid requests - * @function - * @param {string} value - * @returns {{merkleId:string}} - */ + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{eids:arrayofields}} + */ decode(value) { + // Legacy support for a single id const id = (value && value.pam_id && typeof value.pam_id.id === 'string') ? value.pam_id : undefined; logInfo('Merkle id ' + JSON.stringify(id)); - return id ? {'merkleId': id} : undefined; + + if (id) { + return {'merkleId': id} + } + + // Supports multiple IDs for different SSPs + const merkleIds = (value && value?.merkleId && Array.isArray(value.merkleId)) ? value.merkleId : undefined; + logInfo('merkleIds: ' + JSON.stringify(merkleIds)); + + return merkleIds ? {'merkleId': merkleIds} : undefined; }, + /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [config] - * @param {ConsentData} [consentData] - * @returns {IdResponse|undefined} - */ + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @returns {IdResponse|undefined} + */ getId(config, consentData) { logInfo('User ID - merkleId generating id'); const configParams = (config && config.params) || {}; - if (!configParams || typeof configParams.vendor !== 'string') { - logError('User ID - merkleId submodule requires a valid vendor to be defined'); - return; - } - - if (typeof configParams.sv_cid !== 'string') { - logError('User ID - merkleId submodule requires a valid sv_cid string to be defined'); + if (typeof configParams.sv_pubid !== 'string') { + logError('User ID - merkleId submodule requires a valid sv_pubid string to be defined'); return; } - if (typeof configParams.sv_pubid !== 'string') { - logError('User ID - merkleId submodule requires a valid sv_pubid string to be defined'); + if (!Array.isArray(configParams.ssp_ids)) { + logError('User ID - merkleId submodule requires a valid ssp_ids array to be defined'); return; } @@ -132,6 +139,7 @@ export const merkleIdSubmodule = { logError('User ID - merkleId submodule does not currently handle consent strings'); return; } + if (typeof configParams.endpoint !== 'string') { logWarn('User ID - merkleId submodule endpoint string is not defined'); configParams.endpoint = ID_URL @@ -146,12 +154,12 @@ export const merkleIdSubmodule = { return {callback: resp}; }, extendId: function (config = {}, consentData, storedId) { - logInfo('User ID - merkleId stored id ' + storedId); + logInfo('User ID - stored id ' + storedId); const configParams = (config && config.params) || {}; if (typeof configParams.endpoint !== 'string') { logWarn('User ID - merkleId submodule endpoint string is not defined'); - configParams.endpoint = ID_URL + configParams.endpoint = ID_URL; } if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) { @@ -162,25 +170,29 @@ export const merkleIdSubmodule = { if (typeof configParams.sv_domain !== 'string') { configParams.sv_domain = merkleIdSubmodule.findRootDomain(); } + const configStorage = (config && config.storage) || {}; if (configStorage && configStorage.refreshInSeconds && typeof configParams.refreshInSeconds === 'number') { return {id: storedId}; } + let refreshInSeconds = DEFAULT_REFRESH; if (configParams && configParams.refreshInSeconds && typeof configParams.refreshInSeconds === 'number') { refreshInSeconds = configParams.refreshInSeconds; logInfo('User ID - merkleId param refreshInSeconds' + refreshInSeconds); } + const storedDate = new Date(storedId.date); let refreshNeeded = false; if (storedDate) { refreshNeeded = storedDate && (Date.now() - storedDate.getTime() > refreshInSeconds * 1000); if (refreshNeeded) { logInfo('User ID - merkleId needs refreshing id'); - const resp = generateId(configParams, configStorage) + const resp = generateId(configParams, configStorage); return {callback: resp}; } } + logInfo('User ID - merkleId not refreshed'); return {id: storedId}; } diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 51b713c8958..2653f157196 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const GVLID = 358; const DEFAULT_CUR = 'USD'; @@ -87,7 +88,7 @@ export const spec = { let v = nativeParams[k]; const supportProp = spec.NATIVE_ASSET_KEY_TO_ASSET_MAP.hasOwnProperty(k); if (supportProp) { - assetsCount++ + assetsCount++; } if (!isPlainObject(v) || (!supportProp && deepAccess(v, 'required'))) { nativeOk = false; @@ -117,12 +118,16 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + logInfo(LOG_INFO_PREFIX + `buildRequests`); if (validBidRequests.length === 0) { return; } const info = pageInfo(); - const page = info.location || deepAccess(bidderRequest, 'refererInfo.referer') || deepAccess(bidderRequest, 'refererInfo.canonicalUrl'); + // TODO: the fallback seems to never be used here, and probably in the wrong order + const page = info.location || deepAccess(bidderRequest, 'refererInfo.page') const hostname = parseUrl(page).hostname; let domain = extractDomainFromHost(hostname) || hostname; const accountId = setOnAny(validBidRequests, 'params.accountId'); @@ -147,7 +152,7 @@ export const spec = { impObj.bidfloor = floorData.floor; } if (floorData.cur) { - impObj.bidfloorcur = floorData.cur + impObj.bidfloorcur = floorData.cur; } for (let mediaTypes in bid.mediaTypes) { switch (mediaTypes) { diff --git a/modules/microadBidAdapter.js b/modules/microadBidAdapter.js index 982bd61840a..1b31000df43 100644 --- a/modules/microadBidAdapter.js +++ b/modules/microadBidAdapter.js @@ -1,6 +1,7 @@ import { deepAccess, isEmpty, isStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'microad'; @@ -22,6 +23,18 @@ const BANNER_CODE = 1; const NATIVE_CODE = 2; const VIDEO_CODE = 4; +const AUDIENCE_IDS = [ + {type: 6, bidKey: 'userId.imuid'}, + {type: 8, bidKey: 'userId.id5id.uid'}, + {type: 9, bidKey: 'userId.tdid'}, + {type: 10, bidKey: 'userId.novatiq.snowflake'}, + {type: 11, bidKey: 'userId.parrableId.eid'}, + {type: 12, bidKey: 'userId.dacId.id'}, + {type: 13, bidKey: 'userId.idl_env'}, + {type: 14, bidKey: 'userId.criteoId'}, + {type: 15, bidKey: 'userId.pubcid'} +]; + function createCBT() { const randomValue = Math.floor(Math.random() * Math.pow(10, 18)).toString(16); const date = new Date().getTime().toString(16); @@ -45,14 +58,18 @@ export const spec = { return !!(bid && bid.params && bid.params.spot && bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.native || bid.mediaTypes.video)); }, buildRequests: function(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const requests = []; validBidRequests.forEach(bid => { const bidParams = bid.params; const params = { spot: bidParams.spot, - url: bidderRequest.refererInfo.canonicalUrl || window.location.href, - referrer: bidderRequest.refererInfo.referer, + // TODO: are these the right refererInfo values - does the fallback make sense here? + url: bidderRequest.refererInfo.page || window.location.href, + referrer: bidderRequest.refererInfo.ref, bid_id: bid.bidId, transaction_id: bid.transactionId, media_types: convertMediaTypes(bid), @@ -82,9 +99,17 @@ export const spec = { } } - const idlEnv = deepAccess(bid, 'userId.idl_env') - if (!isEmpty(idlEnv) && isStr(idlEnv)) { - params['idl_env'] = idlEnv + const aidsParams = [] + AUDIENCE_IDS.forEach((audienceId) => { + const bidAudienceId = deepAccess(bid, audienceId.bidKey); + if (!isEmpty(bidAudienceId) && isStr(bidAudienceId)) { + aidsParams.push({ type: audienceId.type, id: bidAudienceId }); + // Set Ramp ID + if (audienceId.type === 13) params['idl_env'] = bidAudienceId; + } + }) + if (aidsParams.length > 0) { + params['aids'] = JSON.stringify(aidsParams) } requests.push({ diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index 604a5dd7ea8..d953558bf31 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -286,6 +286,7 @@ function generateBidParameters(bid, bidderRequest) { sizes: sizesArray, floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), bidId: getBidIdParameter('bidId', bid), + loop: getBidIdParameter('bidderRequestsCount', bid), bidderRequestId: getBidIdParameter('bidderRequestId', bid), transactionId: getBidIdParameter('transactionId', bid), }; @@ -390,7 +391,7 @@ function generateGeneralParams(generalObject, bidderRequest) { generalParams.userIds = JSON.stringify(userIdsParam); } - const ortb2Metadata = config.getConfig('ortb2') || {}; + const ortb2Metadata = bidderRequest.ortb2 || {}; if (ortb2Metadata.site) { generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); } @@ -423,8 +424,8 @@ function generateGeneralParams(generalObject, bidderRequest) { } if (bidderRequest && bidderRequest.refererInfo) { - generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); - generalParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); + generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || window.location.href } return generalParams diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 41bae4d6568..2ec9d39fc5d 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -35,7 +35,8 @@ export const spec = { }; if (bidderRequest && bidderRequest.refererInfo) { - payload.referer = bidderRequest.refererInfo.referer; + // TODO: is 'topmostLocation' the right value here? + payload.referer = bidderRequest.refererInfo.topmostLocation; payload.referer_canonical = bidderRequest.refererInfo.canonicalUrl; } @@ -44,6 +45,9 @@ export const spec = { payload.consent_required = bidderRequest.gdprConsent.gdprApplies; } const baseUrl = bidRequest.params.baseUrl || ENDPOINT_URL; + if (bidRequest.params.test) { + payload.test = bidRequest.params.test; + } return { method: 'POST', url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), @@ -68,7 +72,30 @@ export const spec = { return bidResponses; }, + getUserSyncs: function ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) { + if (!syncOptions.iframeEnabled) { + return []; + } + let gdprParams = ''; + if ( + gdprConsent && + 'gdprApplies' in gdprConsent && + typeof gdprConsent.gdprApplies === 'boolean' + ) { + gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${ + gdprConsent.consentString + }`; + } + return [ + { type: 'iframe', url: 'https://sync.missena.io/iframe' + gdprParams }, + ]; + }, /** * Register bidder specific code, which will execute if bidder timed out after an auction * @param {data} Containing timeout specific data diff --git a/modules/mobfoxBidAdapter.md b/modules/mobfoxBidAdapter.md deleted file mode 100644 index 31b60606d2f..00000000000 --- a/modules/mobfoxBidAdapter.md +++ /dev/null @@ -1,29 +0,0 @@ -# Overview - -``` -Module Name: Mobfox Bidder Adapter -Module Type: Bidder Adapter -Maintainer: solutions-team@matomy.com -``` - -# Description - -Module that connects to Mobfox's demand sources - -# Test Parameters -``` - var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - sizes: [[320, 480], [300, 250], [300,600]], - - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'mobfox', - params: { - s: "267d72ac3f77a3f447b32cf7ebf20673", // required - The hash of your inventory to identify which app is making the request, - imp_instl: 1 // optional - set to 1 if using interstitial otherwise delete or set to 0 - } - }] - - }]; -``` diff --git a/modules/mobfoxpbBidAdapter.js b/modules/mobfoxpbBidAdapter.js index a4af7133370..9ff50e2531f 100644 --- a/modules/mobfoxpbBidAdapter.js +++ b/modules/mobfoxpbBidAdapter.js @@ -1,6 +1,7 @@ import { isFn, deepAccess, getWindowTop } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'mobfoxpb'; const AD_URL = 'https://bes.mobfox.com/pbjs'; @@ -48,6 +49,8 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const winTop = getWindowTop(); const location = winTop.location; const placements = []; @@ -66,7 +69,7 @@ export const spec = { request.ccpa = bidderRequest.uspConsent; } if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent + request.gdpr = bidderRequest.gdprConsent; } } @@ -79,7 +82,7 @@ export const spec = { schain: bid.schain || {}, bidfloor: getBidFloor(bid) }; - const mediaType = bid.mediaTypes + const mediaType = bid.mediaTypes; if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { placement.traffic = BANNER; diff --git a/modules/mobsmartBidAdapter.md b/modules/mobsmartBidAdapter.md deleted file mode 100644 index 1240d6db494..00000000000 --- a/modules/mobsmartBidAdapter.md +++ /dev/null @@ -1,50 +0,0 @@ -# Overview - -``` -Module Name: Mobsmart Bidder Adapter -Module Type: Bidder Adapter -Maintainer: adx@kpis.jp -``` - -# Description - -Module that connects to Mobsmart demand sources to fetch bids. - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300, 250]], // a display size - } - }, - bids: [ - { - bidder: "mobsmart", - params: { - floorPrice: 100, - currency: 'JPY' - } - } - ] - },{ - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[320, 50]], // a mobile size - } - }, - bids: [ - { - bidder: "mobsmart", - params: { - floorPrice: 90, - currency: 'JPY' - } - } - ] - } - ]; -``` diff --git a/modules/my6senseBidAdapter.js b/modules/my6senseBidAdapter.js index b4fc1049304..27eb9a9541d 100644 --- a/modules/my6senseBidAdapter.js +++ b/modules/my6senseBidAdapter.js @@ -1,5 +1,6 @@ import { BANNER, NATIVE } from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'my6sense'; const END_POINT = 'https://hb.mynativeplatform.com/pub2/web/v1.15.0/hbwidget.json'; @@ -11,6 +12,7 @@ function isBidRequestValid(bid) { } function getUrl(url) { + // TODO: this should probably look at refererInfo if (!url) { url = window.location.href;// "clean" url of current web page } @@ -118,6 +120,9 @@ function buildGdprServerProperty(bidderRequest) { } function buildRequests(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let requests = []; if (validBidRequests && validBidRequests.length) { diff --git a/modules/mytargetBidAdapter.js b/modules/mytargetBidAdapter.js index f55f2e6b802..b9ce8b133d1 100644 --- a/modules/mytargetBidAdapter.js +++ b/modules/mytargetBidAdapter.js @@ -51,7 +51,7 @@ export const spec = { let referrer = ''; if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.referer; + referrer = bidderRequest.refererInfo.page; } const payload = { diff --git a/modules/nafdigitalBidAdapter.md b/modules/nafdigitalBidAdapter.md deleted file mode 100644 index b17b1f13e1e..00000000000 --- a/modules/nafdigitalBidAdapter.md +++ /dev/null @@ -1,38 +0,0 @@ -# Overview - -``` -Module Name: NAF Digital Bid Adapter -Module Type: Bidder Adapter -Maintainer: vyatsun@gmail.com -``` - -# Description - -NAF Digital adapter integration to the Prebid library. - -# Test Parameters - -``` -var adUnits = [ - { - code: 'test-leaderboard', - sizes: [[728, 90]], - bids: [{ - bidder: 'nafdigital', - params: { - publisherId: 2141020, - bidFloor: 0.01 - } - }] - }, { - code: 'test-banner', - sizes: [[300, 250]], - bids: [{ - bidder: 'nafdigital', - params: { - publisherId: 2141020 - } - }] - } -] -``` diff --git a/modules/nanointeractiveBidAdapter.md b/modules/nanointeractiveBidAdapter.md deleted file mode 100644 index c1790ff6337..00000000000 --- a/modules/nanointeractiveBidAdapter.md +++ /dev/null @@ -1,152 +0,0 @@ -# Overview - -``` -Module Name: Nano Interactive Bid Adapter -Module Type: Bidder Adapter -Maintainer: rade@nanointeractive.com -``` - -# Description - -Connects to Nano Interactive search retargeting Ad Server for bids. - - - -
-### Requirements: -To be able to get identification key (`pid`), please contact us at
-`https://www.nanointeractive.com/publishers`
-


- -#### Send All Bids Ad Server Keys: -(truncated to 20 chars due to [DFP limit](https://support.google.com/dfp_premium/answer/1628457?hl=en#Key-values)) - -`hb_adid_nanointeract` -`hb_bidder_nanointera` -`hb_pb_nanointeractiv` -`hb_format_nanointera` -`hb_size_nanointeract` -`hb_source_nanointera` - -#### Default Deal ID Keys: -`hb_deal_nanointeract` - -### bid params - -{: .table .table-bordered .table-striped } -| Name | Scope | Description | Example | -| :------------- | :------- | :----------------------------------------------- | :--------------------------- | -| `pid` | required | Identification key, provided by Nano Interactive | `'5afaa0280ae8996eb578de53'` | -| `category` | optional | Contextual taxonomy | `'automotive'` | -| `categoryName` | optional | Contextual taxonomy (from URL query param) | `'cat_name'` | -| `nq` | optional | User search query | `'automobile search query'` | -| `name` | optional | User search query (from URL query param) | `'search_param'` | -| `subId` | optional | Channel - used to separate traffic sources | `'123'` | - -#### Configuration -The `category` and `categoryName` are mutually exclusive. If you pass both, `categoryName` takes precedence. -
-The `nq` and `name` are mutually exclusive. If you pass both, `name` takes precedence. - -#### Example with only required field `pid` - var adUnits = [{ - code: 'nano-div', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'nanointeractive', - params: { - pid: '5afaa0280ae8996eb578de53' - } - }] - }]; - -#### Example with `category` - var adUnits = [{ - code: 'nano-div', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'nanointeractive', - params: { - pid: '5afaa0280ae8996eb578de53', - category: 'automotive', - subId: '123' - } - }] - }]; - -#### Example with `categoryName` - var adUnits = [{ - code: 'nano-div', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'nanointeractive', - params: { - pid: '5afaa0280ae8996eb578de53', - // Category "automotive" is in the URL like: - // https://www....?cat_name=automotive&... - categoryName: 'cat_name', - subId: '123' - } - }] - }]; - -#### Example with `nq` - var adUnits = [{ - code: 'nano-div', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'nanointeractive', - params: { - pid: '5afaa0280ae8996eb578de53', - // User searched "automobile search query" (extracted from search text field) - nq: 'automobile search query', - subId: '123' - } - }] - }]; - -#### Example with `name` - var adUnits = [{ - code: 'nano-div', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'nanointeractive', - params: { - pid: '5afaa0280ae8996eb578de53', - // User searched "automobile search query" and it is in the URL like: - // https://www....?search_param=automobile%20search%20query&... - name: 'search_param', - subId: '123' - } - }] - }]; - -#### Example with `category` and `nq` - var adUnits = [{ - code: 'nano-div', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'nanointeractive', - params: { - pid: '5afaa0280ae8996eb578de53', - category: 'automotive', - nq: 'automobile search query', - subId: '123' - } - }] - }]; - -#### Example with `categoryName` and `name` - var adUnits = [{ - code: 'nano-div', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'nanointeractive', - params: { - pid: '5afaa0280ae8996eb578de53', - categoryName: 'cat_name', - name: 'search_param', - subId: '123' - } - }] - }]; \ No newline at end of file diff --git a/modules/nasmediaAdmixerBidAdapter.md b/modules/nasmediaAdmixerBidAdapter.md deleted file mode 100644 index 096acf27f61..00000000000 --- a/modules/nasmediaAdmixerBidAdapter.md +++ /dev/null @@ -1,36 +0,0 @@ -# Overview - -``` -Module Name: NasmediaAdmixer Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid@nasmedia.co.kr -``` -`` -# Description - -Module that connects to NasmediaAdmixer demand sources. -Banner formats are supported. -The NasmediaAdmixer adapter doesn't support multiple sizes per ad-unit and will use the first one if multiple sizes are defined. - -# Test Parameters -``` - var adUnits = [ - { - code: 'banner-ad-div', - mediaTypes: { - banner: { // banner size - sizes: [[300, 250]] - } - }, - bids: [ - { - bidder: 'nasmediaAdmixer', - params: { - media_key: '19038695', //required - adunit_id: '24190632', //required - } - } - ] - } - ]; -``` diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index e07a124665f..54c99b17834 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -11,12 +11,14 @@ const GVLID = 263 const TIME_TO_LIVE = 360 const SUPPORTED_AD_TYPES = [BANNER] +const FLOOR_PRICE_CURRENCY = 'USD' +const PRICE_FLOOR_WILDCARD = '*' /** * Keep track of bid data by keys * @returns {Object} - Map of bid data that can be referenced by multiple keys */ -const BidDataMap = () => { +export const BidDataMap = () => { const referenceMap = {} const bids = [] @@ -25,7 +27,7 @@ const BidDataMap = () => { * @param {String} key - The key to store the index reference * @param {Integer} index - The index value of the bidData */ - function adKeyReference(key, index) { + function addKeyReference(key, index) { if (!referenceMap.hasOwnProperty(key)) { referenceMap[key] = index } @@ -42,12 +44,12 @@ const BidDataMap = () => { if (Array.isArray(keys)) { keys.forEach((key) => { - adKeyReference(String(key), index) + addKeyReference(String(key), index) }) return } - adKeyReference(String(keys), index) + addKeyReference(String(keys), index) } /** @@ -131,72 +133,106 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + // Parse values from bid requests const placementIds = new Set() - let placementId, pageUrl const bidDataMap = BidDataMap() - validBidRequests.forEach((request) => { + const placementSizes = { length: 0 } + const floorPriceData = {} + let placementId, pageUrl + validBidRequests.forEach((bidRequest) => { pageUrl = deepAccess( - request, + bidRequest, 'params.url', - bidderRequest.refererInfo.referer ) - placementId = deepAccess(request, 'params.placementId') + if (pageUrl == undefined || pageUrl === '') { + pageUrl = bidderRequest.refererInfo.page + } + + placementId = deepAccess(bidRequest, 'params.placementId') - if (placementId) { + const bidDataKeys = [bidRequest.adUnitCode] + + if (placementId && !placementIds.has(placementId)) { placementIds.add(placementId) + bidDataKeys.push(placementId) + + placementSizes[placementId] = bidRequest.sizes + placementSizes.length++ } const bidData = { - bidId: request.bidId, - size: getLargestSize(request.sizes), + bidId: bidRequest.bidId, + size: getLargestSize(bidRequest.sizes), + } + bidDataMap.addBidData(bidData, bidDataKeys) + + const bidRequestFloorPriceData = parseFloorPriceData(bidRequest) + if (bidRequestFloorPriceData) { + floorPriceData[bidRequest.adUnitCode] = bidRequestFloorPriceData } - bidDataMap.addBidData(bidData, [placementId, request.adUnitCode]) }) bidRequestMap[bidderRequest.bidderRequestId] = bidDataMap // Build adUnit data - const adUnitData = { - adUnits: validBidRequests.map((adUnit) => { - // Track if we've already requested for this ad unit code - adUnitsRequested[adUnit.adUnitCode] = - adUnitsRequested[adUnit.adUnitCode] !== undefined - ? adUnitsRequested[adUnit.adUnitCode] + 1 - : 0 - return { - adUnitCode: adUnit.adUnitCode, - mediaTypes: adUnit.mediaTypes, - } - }), - } + const adUnitData = buildAdUnitData(validBidRequests) - // Build QS Params + // Build basic required QS Params let params = [ + // Prebid request id { key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId }, + // Ad unit data { key: 'ntv_ppc', value: btoa(JSON.stringify(adUnitData)), // Convert to Base 64 }, + // Number count of requests per ad unit { key: 'ntv_dbr', - value: btoa(JSON.stringify(adUnitsRequested)), + value: btoa(JSON.stringify(adUnitsRequested)), // Convert to Base 64 }, + // Page url { key: 'ntv_url', value: encodeURIComponent(pageUrl), }, ] + // Floor pricing + if (Object.keys(floorPriceData).length) { + params.unshift({ + key: 'ntv_ppf', + value: btoa(JSON.stringify(floorPriceData)), + }) + } + // Add filtering if (adsToFilter.size > 0) { - params.unshift({ key: 'ntv_atf', value: Array.from(adsToFilter).join(',') }) + params.unshift({ + key: 'ntv_atf', + value: Array.from(adsToFilter).join(','), + }) } if (advertisersToFilter.size > 0) { - params.unshift({ key: 'ntv_avtf', value: Array.from(advertisersToFilter).join(',') }) + params.unshift({ + key: 'ntv_avtf', + value: Array.from(advertisersToFilter).join(','), + }) } if (campaignsToFilter.size > 0) { - params.unshift({ key: 'ntv_ctf', value: Array.from(campaignsToFilter).join(',') }) + params.unshift({ + key: 'ntv_ctf', + value: Array.from(campaignsToFilter).join(','), + }) + } + + // Placement Sizes + if (placementSizes.length) { + params.unshift({ + key: 'ntv_pas', + value: btoa(JSON.stringify(placementSizes)), + }) } // Add placement IDs @@ -417,6 +453,126 @@ export const spec = { registerBidder(spec) // Utils +export function parseFloorPriceData(bidRequest) { + if (typeof bidRequest.getFloor !== 'function') return + + // Setup price floor data per bid request + let bidRequestFloorPriceData = {} + let bidMediaTypes = bidRequest.mediaTypes + let sizeOptions = new Set() + // Step through meach media type so we can get floor data for each media type per bid request + Object.keys(bidMediaTypes).forEach((mediaType) => { + // Setup price floor data per media type + let mediaTypeData = bidMediaTypes[mediaType] + let mediaTypeFloorPriceData = {} + // Step through each size of the media type so we can get floor data for each size per media type + mediaTypeData.sizes.forEach((size) => { + // Get floor price data per the getFloor method and respective media type / size combination + const priceFloorData = bidRequest.getFloor({ + currency: FLOOR_PRICE_CURRENCY, + mediaType, + size, + }) + // Save the data and track the sizes + mediaTypeFloorPriceData[sizeToString(size)] = priceFloorData.floor + sizeOptions.add(size) + }) + bidRequestFloorPriceData[mediaType] = mediaTypeFloorPriceData + + // Get floor price of current media type with a wildcard size + const sizeWildcardFloor = getSizeWildcardPrice(bidRequest, mediaType) + // Save the wildcard floor price if it was retrieved successfully + if (sizeWildcardFloor.floor > 0) { + mediaTypeFloorPriceData['*'] = sizeWildcardFloor.floor + } + }) + + // Get floor price for wildcard media type using all of the sizes present in the previous media types + const mediaWildCardPrices = getMediaWildcardPrices(bidRequest, [ + PRICE_FLOOR_WILDCARD, + ...Array.from(sizeOptions), + ]) + bidRequestFloorPriceData['*'] = mediaWildCardPrices + + return bidRequestFloorPriceData +} + +/** + * Get price floor data by always setting the size value to the wildcard for a specific size + * @param {Object} bidRequest - The bid request + * @param {String} mediaType - The media type + * @returns {Object} - Bid floor data + */ +export function getSizeWildcardPrice(bidRequest, mediaType) { + return bidRequest.getFloor({ + currency: FLOOR_PRICE_CURRENCY, + mediaType, + size: PRICE_FLOOR_WILDCARD, + }) +} + +/** + * Get price data for a range of sizes and always setting the media type to the wildcard value + * @param {*} bidRequest - The bid request + * @param {*} sizes - The sizes to get the floor price data for + * @returns {Object} - Bid floor data + */ +export function getMediaWildcardPrices( + bidRequest, + sizes = [PRICE_FLOOR_WILDCARD] +) { + const sizePrices = {} + sizes.forEach((size) => { + // MODIFY the bid request's mediaTypes property (so we can get the wildcard media type value) + const temp = bidRequest.mediaTypes + bidRequest.mediaTypes = { PRICE_FLOOR_WILDCARD: temp.sizes } + // Get price floor data + const priceFloorData = bidRequest.getFloor({ + currency: FLOOR_PRICE_CURRENCY, + mediaType: PRICE_FLOOR_WILDCARD, + size, + }) + // RESTORE initial property value + bidRequest.mediaTypes = temp + + // Only save valid floor price data + const key = + size !== PRICE_FLOOR_WILDCARD ? sizeToString(size) : PRICE_FLOOR_WILDCARD + sizePrices[key] = priceFloorData.floor + }) + return sizePrices +} + +/** + * Format size array to a string + * @param {Array} size - Size data [width, height] + * @returns {String} - Formated size string + */ +export function sizeToString(size) { + if (!Array.isArray(size) || size.length < 2) return '' + return `${size[0]}x${size[1]}` +} + +/** + * Build the ad unit data to send back to the request endpoint + * @param {Array} requests - Bid requests + * @returns {Array} - Array of ad unit data + */ +function buildAdUnitData(requests) { + return requests.map((request) => { + // Track if we've already requested for this ad unit code + adUnitsRequested[request.adUnitCode] = + adUnitsRequested[request.adUnitCode] !== undefined + ? adUnitsRequested[request.adUnitCode] + 1 + : 0 + // Return a new object with only the data we need + return { + adUnitCode: request.adUnitCode, + mediaTypes: request.mediaTypes, + } + }) +} + /** * Append QS param to existing string * @param {String} str - String to append to diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index d7f5f712252..a292b42f144 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -34,17 +34,6 @@ function readnavIDFromCookie() { return storage.cookiesAreEnabled ? (storage.findSimilarCookies('nav') ? storage.findSimilarCookies('nav')[0] : null) : null; } -function readnvgnavFromLocalStorage() { - var i; - const query = /[nvga]{3}\d+/; - for (i in window.localStorage) { - if (i.match(query) || (!query && typeof i === 'string')) { - const naveggId = storage.getDataFromLocalStorage(i.match(query).input).split('|')[0] - return naveggId.split('_')[0]; - } - } -} - /** @type {Submodule} */ export const naveggIdSubmodule = { /** @@ -73,7 +62,7 @@ export const naveggIdSubmodule = { getId() { let naveggIdStringFromLocalStorage = null; if (storage.localStorageIsEnabled) { - naveggIdStringFromLocalStorage = readnaveggIdFromLocalStorage() || readnvgnavFromLocalStorage(); + naveggIdStringFromLocalStorage = readnaveggIdFromLocalStorage(); } const naveggIdString = naveggIdStringFromLocalStorage || readnaveggIDFromCookie() || readoldnaveggIDFromCookie() || readnvgIDFromCookie() || readnavIDFromCookie(); diff --git a/modules/newborntownWebBidAdapter.md b/modules/newborntownWebBidAdapter.md deleted file mode 100644 index f607369ffb6..00000000000 --- a/modules/newborntownWebBidAdapter.md +++ /dev/null @@ -1,35 +0,0 @@ -# Overview - -``` -Module Name: NewborntownWeb Bidder Adapter -Module Type: Bidder Adapter -Maintainer: zhuyushuang@newborntown.com -``` - -# Description - -Integration for website - -# Test Parameters -``` - var adUnits = [ - { - code: '/19968336/header-bid-tag-1', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [ - { - bidder: "newborntownWeb", - params: { - 'publisher_id': '1238122', - 'slot_id': '123123', - 'bidfloor': 0.2 - } - } - ] - } - ]; -``` diff --git a/modules/newspassidBidAdapter.js b/modules/newspassidBidAdapter.js new file mode 100644 index 00000000000..409cb55e6a0 --- /dev/null +++ b/modules/newspassidBidAdapter.js @@ -0,0 +1,649 @@ +import { logInfo, logError, deepAccess, logWarn, deepSetValue, isArray, contains, parseUrl } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {getPriceBucketString} from '../src/cpmBucketManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +const BIDDER_CODE = 'newspassid'; +const ORIGIN = 'https://bidder.newspassid.com' // applies only to auction & cookie +const AUCTIONURI = '/openrtb2/auction'; +const NEWSPASSCOOKIESYNC = '/static/load-cookie.html'; +const NEWSPASSVERSION = '1.0.1'; +export const spec = { + version: NEWSPASSVERSION, + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + cookieSyncBag: {publisherId: null, siteId: null, userIdObject: {}}, // variables we want to make available to cookie sync + propertyBag: {config: null, pageId: null, buildRequestsStart: 0, buildRequestsEnd: 0, endpointOverride: null}, /* allow us to store vars in instance scope - needs to be an object to be mutable */ + config_defaults: { + 'logId': 'NEWSPASSID', + 'bidder': 'newspassid', + 'auctionUrl': ORIGIN + AUCTIONURI, + 'cookieSyncUrl': ORIGIN + NEWSPASSCOOKIESYNC + }, + loadConfiguredData(bid) { + if (this.propertyBag.config) { return; } + this.propertyBag.config = JSON.parse(JSON.stringify(this.config_defaults)); + let bidder = bid.bidder || 'newspassid'; + this.propertyBag.config.logId = bidder.toUpperCase(); + this.propertyBag.config.bidder = bidder; + let bidderConfig = config.getConfig(bidder) || {}; + logInfo('got bidderConfig: ', JSON.parse(JSON.stringify(bidderConfig))); + let arrGetParams = this.getGetParametersAsObject(); + if (bidderConfig.endpointOverride) { + if (bidderConfig.endpointOverride.origin) { + this.propertyBag.endpointOverride = bidderConfig.endpointOverride.origin; + this.propertyBag.config.auctionUrl = bidderConfig.endpointOverride.origin + AUCTIONURI; + this.propertyBag.config.cookieSyncUrl = bidderConfig.endpointOverride.origin + NEWSPASSCOOKIESYNC; + } + if (bidderConfig.endpointOverride.cookieSyncUrl) { + this.propertyBag.config.cookieSyncUrl = bidderConfig.endpointOverride.cookieSyncUrl; + } + if (bidderConfig.endpointOverride.auctionUrl) { + this.propertyBag.endpointOverride = bidderConfig.endpointOverride.auctionUrl; + this.propertyBag.config.auctionUrl = bidderConfig.endpointOverride.auctionUrl; + } + } + try { + if (arrGetParams.hasOwnProperty('auction')) { + logInfo('GET: setting auction endpoint to: ' + arrGetParams.auction); + this.propertyBag.config.auctionUrl = arrGetParams.auction; + } + if (arrGetParams.hasOwnProperty('cookiesync')) { + logInfo('GET: setting cookiesync to: ' + arrGetParams.cookiesync); + this.propertyBag.config.cookieSyncUrl = arrGetParams.cookiesync; + } + } catch (e) {} + logInfo('set propertyBag.config to', this.propertyBag.config); + }, + getAuctionUrl() { + return this.propertyBag.config.auctionUrl; + }, + getCookieSyncUrl() { + return this.propertyBag.config.cookieSyncUrl; + }, + isBidRequestValid(bid) { + this.loadConfiguredData(bid); + logInfo('isBidRequestValid : ', config.getConfig(), bid); + let adUnitCode = bid.adUnitCode; // adunit[n].code + let err1 = 'VALIDATION FAILED : missing {param} : siteId, placementId and publisherId are REQUIRED'; + if (!(bid.params.hasOwnProperty('placementId'))) { + logError(err1.replace('{param}', 'placementId'), adUnitCode); + return false; + } + if (!this.isValidPlacementId(bid.params.placementId)) { + logError('VALIDATION FAILED : placementId must be exactly 10 numeric characters', adUnitCode); + return false; + } + if (!(bid.params.hasOwnProperty('publisherId'))) { + logError(err1.replace('{param}', 'publisherId'), adUnitCode); + return false; + } + if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { + logError('VALIDATION FAILED : publisherId must be exactly 12 alphanumeric characters including hyphens', adUnitCode); + return false; + } + if (!(bid.params.hasOwnProperty('siteId'))) { + logError(err1.replace('{param}', 'siteId'), adUnitCode); + return false; + } + if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { + logError('VALIDATION FAILED : siteId must be exactly 10 numeric characters', adUnitCode); + return false; + } + if (bid.params.hasOwnProperty('customParams')) { + logError('VALIDATION FAILED : customParams should be renamed to customData', adUnitCode); + return false; + } + if (bid.params.hasOwnProperty('customData')) { + if (!Array.isArray(bid.params.customData)) { + logError('VALIDATION FAILED : customData is not an Array', adUnitCode); + return false; + } + if (bid.params.customData.length < 1) { + logError('VALIDATION FAILED : customData is an array but does not contain any elements', adUnitCode); + return false; + } + if (!(bid.params.customData[0]).hasOwnProperty('targeting')) { + logError('VALIDATION FAILED : customData[0] does not contain "targeting"', adUnitCode); + return false; + } + if (typeof bid.params.customData[0]['targeting'] != 'object') { + logError('VALIDATION FAILED : customData[0] targeting is not an object', adUnitCode); + return false; + } + } + return true; + }, + isValidPlacementId(placementId) { + return placementId.toString().match(/^[0-9]{10}$/); + }, + buildRequests(validBidRequests, bidderRequest) { + this.loadConfiguredData(validBidRequests[0]); + this.propertyBag.buildRequestsStart = new Date().getTime(); + logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${NEWSPASSVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); + if (this.blockTheRequest()) { + return []; + } + let htmlParams = {'publisherId': '', 'siteId': ''}; + if (validBidRequests.length > 0) { + this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIds(validBidRequests[0])); + this.cookieSyncBag.siteId = deepAccess(validBidRequests[0], 'params.siteId'); + this.cookieSyncBag.publisherId = deepAccess(validBidRequests[0], 'params.publisherId'); + htmlParams = validBidRequests[0].params; + } + logInfo('cookie sync bag', this.cookieSyncBag); + let singleRequest = config.getConfig('newspassid.singleRequest'); + singleRequest = singleRequest !== false; // undefined & true will be true + logInfo(`config newspassid.singleRequest : `, singleRequest); + let npRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params + logInfo('going to get ortb2 from bidder request...'); + let fpd = deepAccess(bidderRequest, 'ortb2', null); + logInfo('got fpd: ', fpd); + if (fpd && deepAccess(fpd, 'user')) { + logInfo('added FPD user object'); + npRequest.user = fpd.user; + } + const getParams = this.getGetParametersAsObject(); + const isTestMode = getParams['nptestmode'] || null; // this can be any string, it's used for testing ads + npRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; + let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string + let schain = null; + let tosendtags = validBidRequests.map(npBidRequest => { + var obj = {}; + let placementId = placementIdOverrideFromGetParam || this.getPlacementId(npBidRequest); // prefer to use a valid override param, else the bidRequest placement Id + obj.id = npBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder newspass made bid for unknown request ID: mb7953.859498327448. Ignoring." + obj.tagid = placementId; + let parsed = parseUrl(getRefererInfo().page); + obj.secure = parsed.protocol === 'https' ? 1 : 0; + let arrBannerSizes = []; + if (!npBidRequest.hasOwnProperty('mediaTypes')) { + if (npBidRequest.hasOwnProperty('sizes')) { + logInfo('no mediaTypes detected - will use the sizes array in the config root'); + arrBannerSizes = npBidRequest.sizes; + } else { + logInfo('Cannot set sizes for banner type'); + } + } else { + if (npBidRequest.mediaTypes.hasOwnProperty(BANNER)) { + arrBannerSizes = npBidRequest.mediaTypes[BANNER].sizes; /* Note - if there is a sizes element in the config root it will be pushed into here */ + logInfo('setting banner size from the mediaTypes.banner element for bidId ' + obj.id + ': ', arrBannerSizes); + } + if (npBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { + obj.native = npBidRequest.mediaTypes[NATIVE]; + logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); + } + } + if (arrBannerSizes.length > 0) { + obj.banner = { + topframe: 1, + w: arrBannerSizes[0][0] || 0, + h: arrBannerSizes[0][1] || 0, + format: arrBannerSizes.map(s => { + return {w: s[0], h: s[1]}; + }) + }; + } + obj.placementId = placementId; + deepSetValue(obj, 'ext.prebid', {'storedrequest': {'id': placementId}}); + obj.ext['newspassid'] = {}; + obj.ext['newspassid'].adUnitCode = npBidRequest.adUnitCode; // eg. 'mpu' + obj.ext['newspassid'].transactionId = npBidRequest.transactionId; // this is the transactionId PER adUnit, common across bidders for this unit + if (npBidRequest.params.hasOwnProperty('customData')) { + obj.ext['newspassid'].customData = npBidRequest.params.customData; + } + logInfo(`obj.ext.newspassid is `, obj.ext['newspassid']); + if (isTestMode != null) { + logInfo('setting isTestMode to ', isTestMode); + if (obj.ext['newspassid'].hasOwnProperty('customData')) { + for (let i = 0; i < obj.ext['newspassid'].customData.length; i++) { + obj.ext['newspassid'].customData[i]['targeting']['nptestmode'] = isTestMode; + } + } else { + obj.ext['newspassid'].customData = [{'settings': {}, 'targeting': {}}]; + obj.ext['newspassid'].customData[0].targeting['nptestmode'] = isTestMode; + } + } + if (fpd && deepAccess(fpd, 'site')) { + logInfo('adding fpd.site'); + if (deepAccess(obj, 'ext.newspassid.customData.0.targeting', false)) { + obj.ext.newspassid.customData[0].targeting = Object.assign(obj.ext.newspassid.customData[0].targeting, fpd.site); + } else { + deepSetValue(obj, 'ext.newspassid.customData.0.targeting', fpd.site); + } + } + if (!schain && deepAccess(npBidRequest, 'schain')) { + schain = npBidRequest.schain; + } + return obj; + }); + let extObj = {}; + extObj['newspassid'] = {}; + extObj['newspassid']['np_pb_v'] = NEWSPASSVERSION; + extObj['newspassid']['np_rw'] = placementIdOverrideFromGetParam ? 1 : 0; + if (validBidRequests.length > 0) { + let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info + if (userIds.hasOwnProperty('pubcid')) { + extObj['newspassid'].pubcid = userIds.pubcid; + } + } + extObj['newspassid'].pv = this.getPageId(); // attach the page ID that will be common to all auction calls for this page if refresh() is called + let whitelistAdserverKeys = config.getConfig('newspassid.np_whitelist_adserver_keys'); + let useWhitelistAdserverKeys = isArray(whitelistAdserverKeys) && whitelistAdserverKeys.length > 0; + extObj['newspassid']['np_kvp_rw'] = useWhitelistAdserverKeys ? 1 : 0; + if (getParams.hasOwnProperty('npf')) { extObj['newspassid']['npf'] = getParams.npf === 'true' || getParams.npf === '1' ? 1 : 0; } + if (getParams.hasOwnProperty('nppf')) { extObj['newspassid']['nppf'] = getParams.nppf === 'true' || getParams.nppf === '1' ? 1 : 0; } + if (getParams.hasOwnProperty('nprp') && getParams.nprp.match(/^[0-3]$/)) { extObj['newspassid']['nprp'] = parseInt(getParams.nprp); } + if (getParams.hasOwnProperty('npip') && getParams.npip.match(/^\d+$/)) { extObj['newspassid']['npip'] = parseInt(getParams.npip); } + if (this.propertyBag.endpointOverride != null) { extObj['newspassid']['origin'] = this.propertyBag.endpointOverride; } + let userExtEids = deepAccess(validBidRequests, '0.userIdAsEids', []); // generate the UserIDs in the correct format for UserId module + npRequest.site = { + 'publisher': {'id': htmlParams.publisherId}, + 'page': getRefererInfo().page, + 'id': htmlParams.siteId + }; + npRequest.test = config.getConfig('debug') ? 1 : 0; + if (bidderRequest && bidderRequest.uspConsent) { + logInfo('ADDING USP consent info'); + deepSetValue(npRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } else { + logInfo('WILL NOT ADD USP consent info; no bidderRequest.uspConsent.'); + } + if (schain) { // we set this while iterating over the bids + logInfo('schain found'); + deepSetValue(npRequest, 'source.ext.schain', schain); + } + if (config.getConfig('coppa') === true) { + deepSetValue(npRequest, 'regs.coppa', 1); + } + if (singleRequest) { + logInfo('buildRequests starting to generate response for a single request'); + npRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. + npRequest.auctionId = bidderRequest.auctionId; // not sure if this should be here? + npRequest.imp = tosendtags; + npRequest.ext = extObj; + deepSetValue(npRequest, 'source.tid', bidderRequest.auctionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). + deepSetValue(npRequest, 'user.ext.eids', userExtEids); + var ret = { + method: 'POST', + url: this.getAuctionUrl(), + data: JSON.stringify(npRequest), + bidderRequest: bidderRequest + }; + logInfo('buildRequests request data for single = ', JSON.parse(JSON.stringify(npRequest))); + this.propertyBag.buildRequestsEnd = new Date().getTime(); + logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); + return ret; + } + let arrRet = tosendtags.map(imp => { + logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); + let npRequestSingle = Object.assign({}, npRequest); + imp.ext['newspassid'].pageAuctionId = bidderRequest['auctionId']; // make a note in the ext object of what the original auctionId was, in the bidderRequest object + npRequestSingle.id = imp.ext['newspassid'].transactionId; // Unique ID of the bid request, provided by the exchange. + npRequestSingle.auctionId = imp.ext['newspassid'].transactionId; // not sure if this should be here? + npRequestSingle.imp = [imp]; + npRequestSingle.ext = extObj; + deepSetValue(npRequestSingle, 'source.tid', imp.ext['newspassid'].transactionId);// RTB 2.5 : tid is Transaction ID that must be common across all participants in this bid request (e.g., potentially multiple exchanges). + deepSetValue(npRequestSingle, 'user.ext.eids', userExtEids); + logInfo('buildRequests RequestSingle (for non-single) = ', npRequestSingle); + return { + method: 'POST', + url: this.getAuctionUrl(), + data: JSON.stringify(npRequestSingle), + bidderRequest: bidderRequest + }; + }); + this.propertyBag.buildRequestsEnd = new Date().getTime(); + logInfo(`buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); + return arrRet; + }, + interpretResponse(serverResponse, request) { + if (request && request.bidderRequest && request.bidderRequest.bids) { this.loadConfiguredData(request.bidderRequest.bids[0]); } + let startTime = new Date().getTime(); + logInfo(`interpretResponse time: ${startTime}. buildRequests done -> interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); + logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); + serverResponse = serverResponse.body || {}; + if (!serverResponse.hasOwnProperty('seatbid')) { + return []; + } + if (typeof serverResponse.seatbid !== 'object') { + return []; + } + let arrAllBids = []; + let enhancedAdserverTargeting = config.getConfig('newspassid.enhancedAdserverTargeting'); + logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); + if (typeof enhancedAdserverTargeting == 'undefined') { + enhancedAdserverTargeting = true; + } + logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); + serverResponse.seatbid = injectAdIdsIntoAllBidResponses(serverResponse.seatbid); // we now make sure that each bid in the bidresponse has a unique (within page) adId attribute. + serverResponse.seatbid = this.removeSingleBidderMultipleBids(serverResponse.seatbid); + let whitelistAdserverKeys = config.getConfig('newspassid.np_whitelist_adserver_keys'); + let useWhitelistAdserverKeys = isArray(whitelistAdserverKeys) && whitelistAdserverKeys.length > 0; + for (let i = 0; i < serverResponse.seatbid.length; i++) { + let sb = serverResponse.seatbid[i]; + for (let j = 0; j < sb.bid.length; j++) { + let thisRequestBid = this.getBidRequestForBidId(sb.bid[j].impid, request.bidderRequest.bids); + logInfo(`seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); + const {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); + let thisBid = this.addStandardProperties(sb.bid[j], defaultWidth, defaultHeight); + thisBid.meta = {advertiserDomains: thisBid.adomain || []}; + let bidType = deepAccess(thisBid, 'ext.prebid.type'); + logInfo(`this bid type is : ${bidType}`, j); + let adserverTargeting = {}; + if (enhancedAdserverTargeting) { + let allBidsForThisBidid = this.getAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); + logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); + Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { + logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); + adserverTargeting['np_' + bidderName] = bidderName; + adserverTargeting['np_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); + adserverTargeting['np_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); + adserverTargeting['np_' + bidderName + '_adId'] = String(allBidsForThisBidid[bidderName].adId); + adserverTargeting['np_' + bidderName + '_pb_r'] = getRoundedBid(allBidsForThisBidid[bidderName].price, allBidsForThisBidid[bidderName].ext.prebid.type); + if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { + adserverTargeting['np_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); + } + }); + } else { + logInfo(`newspassid.enhancedAdserverTargeting is set to false, no per-bid keys will be sent to adserver.`); + } + let {seat: winningSeat, bid: winningBid} = this.getWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); + adserverTargeting['np_auc_id'] = String(request.bidderRequest.auctionId); + adserverTargeting['np_winner'] = String(winningSeat); + adserverTargeting['np_bid'] = 'true'; + if (enhancedAdserverTargeting) { + adserverTargeting['np_imp_id'] = String(winningBid.impid); + adserverTargeting['np_pb_r'] = getRoundedBid(winningBid.price, bidType); + adserverTargeting['np_adId'] = String(winningBid.adId); + adserverTargeting['np_size'] = `${winningBid.width}x${winningBid.height}`; + } + if (useWhitelistAdserverKeys) { // delete any un-whitelisted keys + logInfo('Going to filter out adserver targeting keys not in the whitelist: ', whitelistAdserverKeys); + Object.keys(adserverTargeting).forEach(function(key) { if (whitelistAdserverKeys.indexOf(key) === -1) { delete adserverTargeting[key]; } }); + } + thisBid.adserverTargeting = adserverTargeting; + arrAllBids.push(thisBid); + } + } + let endTime = new Date().getTime(); + logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); + return arrAllBids; + }, + removeSingleBidderMultipleBids(seatbid) { + var ret = []; + for (let i = 0; i < seatbid.length; i++) { + let sb = seatbid[i]; + var retSeatbid = {'seat': sb.seat, 'bid': []}; + var bidIds = []; + for (let j = 0; j < sb.bid.length; j++) { + var candidate = sb.bid[j]; + if (contains(bidIds, candidate.impid)) { + continue; // we've already fully assessed this impid, found the highest bid from this seat for it + } + bidIds.push(candidate.impid); + for (let k = j + 1; k < sb.bid.length; k++) { + if (sb.bid[k].impid === candidate.impid && sb.bid[k].price > candidate.price) { + candidate = sb.bid[k]; + } + } + retSeatbid.bid.push(candidate); + } + ret.push(retSeatbid); + } + return ret; + }, + getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { + logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); + if (!serverResponse || serverResponse.length === 0) { + return []; + } + if (optionsType.iframeEnabled) { + var arrQueryString = []; + if (config.getConfig('debug')) { + arrQueryString.push('pbjs_debug=true'); + } + arrQueryString.push('usp_consent=' + (usPrivacy || '')); + for (let keyname in this.cookieSyncBag.userIdObject) { + arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); + } + arrQueryString.push('publisherId=' + this.cookieSyncBag.publisherId); + arrQueryString.push('siteId=' + this.cookieSyncBag.siteId); + arrQueryString.push('cb=' + Date.now()); + arrQueryString.push('bidder=' + this.propertyBag.config.bidder); + var strQueryString = arrQueryString.join('&'); + if (strQueryString.length > 0) { + strQueryString = '?' + strQueryString; + } + logInfo('getUserSyncs going to return cookie sync url : ' + this.getCookieSyncUrl() + strQueryString); + return [{ + type: 'iframe', + url: this.getCookieSyncUrl() + strQueryString + }]; + } + }, + getBidRequestForBidId(bidId, arrBids) { + for (let i = 0; i < arrBids.length; i++) { + if (arrBids[i].bidId === bidId) { // bidId in the request comes back as impid in the seatbid bids + return arrBids[i]; + } + } + return null; + }, + findAllUserIds(bidRequest) { + var ret = {}; + let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; + if (bidRequest.hasOwnProperty('userId')) { + for (let arrayId in searchKeysSingle) { + let key = searchKeysSingle[arrayId]; + if (bidRequest.userId.hasOwnProperty(key)) { + if (typeof (bidRequest.userId[key]) == 'string') { + ret[key] = bidRequest.userId[key]; + } else if (typeof (bidRequest.userId[key]) == 'object') { + logError(`WARNING: findAllUserIds had to use first key in user object to get value for bid.userId key: ${key}. Prebid adapter should be updated.`); + ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values + } else { + logError(`failed to get string key value for userId : ${key}`); + } + } + } + let lipbid = deepAccess(bidRequest.userId, 'lipb.lipbid'); + if (lipbid) { + ret['lipb'] = {'lipbid': lipbid}; + } + let id5id = deepAccess(bidRequest.userId, 'id5id.uid'); + if (id5id) { + ret['id5id'] = id5id; + } + let parrableId = deepAccess(bidRequest.userId, 'parrableId.eid'); + if (parrableId) { + ret['parrableId'] = parrableId; + } + let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); + if (sharedid) { + ret['sharedid'] = sharedid; + } + } + if (!ret.hasOwnProperty('pubcid')) { + let pubcid = deepAccess(bidRequest, 'crumbs.pubcid'); + if (pubcid) { + ret['pubcid'] = pubcid; // if built with old pubCommonId module + } + } + return ret; + }, + getPlacementId(bidRequest) { + return (bidRequest.params.placementId).toString(); + }, + getPlacementIdOverrideFromGetParam() { + let arr = this.getGetParametersAsObject(); + if (arr.hasOwnProperty('npstoredrequest')) { + if (this.isValidPlacementId(arr['npstoredrequest'])) { + logInfo(`using GET npstoredrequest ` + arr['npstoredrequest'] + ' to replace placementId'); + return arr['npstoredrequest']; + } else { + logError(`GET npstoredrequest FAILED VALIDATION - will not use it`); + } + } + return null; + }, + getGetParametersAsObject() { + let parsed = parseUrl(getRefererInfo().page); + logInfo('getGetParametersAsObject found:', parsed.search); + return parsed.search; + }, + blockTheRequest() { + let npRequest = config.getConfig('newspassid.np_request'); + if (typeof npRequest == 'boolean' && !npRequest) { + logWarn(`Will not allow auction : np_request is set to false`); + return true; + } + return false; + }, + getPageId: function() { + if (this.propertyBag.pageId == null) { + let randPart = ''; + let allowable = '0123456789abcdefghijklmnopqrstuvwxyz'; + for (let i = 20; i > 0; i--) { + randPart += allowable[Math.floor(Math.random() * 36)]; + } + this.propertyBag.pageId = new Date().getTime() + '_' + randPart; + } + return this.propertyBag.pageId; + }, + addStandardProperties(seatBid, defaultWidth, defaultHeight) { + seatBid.cpm = seatBid.price; + seatBid.bidId = seatBid.impid; + seatBid.requestId = seatBid.impid; + seatBid.width = seatBid.w || defaultWidth; + seatBid.height = seatBid.h || defaultHeight; + seatBid.ad = seatBid.adm; + seatBid.netRevenue = true; + seatBid.creativeId = seatBid.crid; + seatBid.currency = 'USD'; + seatBid.ttl = 300; + return seatBid; + }, + getWinnerForRequestBid(requestBidId, serverResponseSeatBid) { + let thisBidWinner = null; + let winningSeat = null; + for (let j = 0; j < serverResponseSeatBid.length; j++) { + let theseBids = serverResponseSeatBid[j].bid; + let thisSeat = serverResponseSeatBid[j].seat; + for (let k = 0; k < theseBids.length; k++) { + if (theseBids[k].impid === requestBidId) { + if ((thisBidWinner == null) || (thisBidWinner.price < theseBids[k].price)) { + thisBidWinner = theseBids[k]; + winningSeat = thisSeat; + break; + } + } + } + } + return {'seat': winningSeat, 'bid': thisBidWinner}; + }, + getAllBidsForBidId(matchBidId, serverResponseSeatBid) { + let objBids = {}; + for (let j = 0; j < serverResponseSeatBid.length; j++) { + let theseBids = serverResponseSeatBid[j].bid; + let thisSeat = serverResponseSeatBid[j].seat; + for (let k = 0; k < theseBids.length; k++) { + if (theseBids[k].impid === matchBidId) { + if (objBids.hasOwnProperty(thisSeat)) { // > 1 bid for an adunit from a bidder - only use the one with the highest bid + if (objBids[thisSeat]['price'] < theseBids[k].price) { + objBids[thisSeat] = theseBids[k]; + } + } else { + objBids[thisSeat] = theseBids[k]; + } + } + } + } + return objBids; + } +}; +export function injectAdIdsIntoAllBidResponses(seatbid) { + logInfo('injectAdIdsIntoAllBidResponses', seatbid); + for (let i = 0; i < seatbid.length; i++) { + let sb = seatbid[i]; + for (let j = 0; j < sb.bid.length; j++) { + sb.bid[j]['adId'] = `${sb.bid[j]['impid']}-${i}-np-${j}`; + } + } + return seatbid; +} +export function checkDeepArray(Arr) { + if (Array.isArray(Arr)) { + if (Array.isArray(Arr[0])) { + return Arr[0]; + } else { + return Arr; + } + } else { + return Arr; + } +} +export function defaultSize(thebidObj) { + if (!thebidObj) { + logInfo('defaultSize received empty bid obj! going to return fixed default size'); + return { + 'defaultHeight': 250, + 'defaultWidth': 300 + }; + } + const {sizes} = thebidObj; + const returnObject = {}; + returnObject.defaultWidth = checkDeepArray(sizes)[0]; + returnObject.defaultHeight = checkDeepArray(sizes)[1]; + return returnObject; +} +export function getRoundedBid(price, mediaType) { + const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`); // might be string or object or nothing; if set then this takes precedence over 'priceGranularity' + let objBuckets = config.getConfig('customPriceBucket'); // this is always an object - {} if strBuckets is not 'custom' + let strBuckets = config.getConfig('priceGranularity'); // priceGranularity value, always a string ** if priceGranularity is set to an object then it's always 'custom' ** + let theConfigObject = getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets); + let theConfigKey = getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets); + logInfo('getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); + let priceStringsObj = getPriceBucketString( + price, + theConfigObject, + config.getConfig('currency.granularityMultiplier') + ); + logInfo('priceStringsObj', priceStringsObj); + let granularityNamePriceStringsKeyMapping = { + 'medium': 'med', + 'custom': 'custom', + 'high': 'high', + 'low': 'low', + 'dense': 'dense' + }; + if (granularityNamePriceStringsKeyMapping.hasOwnProperty(theConfigKey)) { + let priceStringsKey = granularityNamePriceStringsKeyMapping[theConfigKey]; + logInfo('getRoundedBid: looking for priceStringsKey:', priceStringsKey); + return priceStringsObj[priceStringsKey]; + } + return priceStringsObj['auto']; +} +export function getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets) { + if (typeof mediaTypeGranularity === 'string') { + return mediaTypeGranularity; + } + if (typeof mediaTypeGranularity === 'object') { + return 'custom'; + } + if (typeof strBuckets === 'string') { + return strBuckets; + } + return 'auto'; // fall back to a default key - should literally never be needed. +} +export function getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets) { + if (typeof mediaTypeGranularity === 'object') { + return mediaTypeGranularity; + } + if (strBuckets === 'custom') { + return objBuckets; + } + return ''; +} +registerBidder(spec); +logInfo(`*BidAdapter ${NEWSPASSVERSION} was loaded`); diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 91508d38ca0..87d2e53568f 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,4 +1,5 @@ import { isStr, _each, parseUrl, getWindowTop, getBidIdParameter } from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; @@ -24,19 +25,42 @@ export const spec = { _each(validBidRequests, function(bid) { window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; + const id = getPlacementId(bid) + let sizes = bid.sizes + if (sizes && !Array.isArray(sizes[0])) sizes = [sizes] + + const site = getSiteObj() + const device = getDeviceObj() + const postBody = { 'id': bid.auctionId, 'ext': { 'prebid': { 'storedrequest': { - 'id': getPlacementId(bid) + 'id': id } }, 'nextMillennium': { 'refresh_count': window.nmmRefreshCounts[bid.adUnitCode]++, + 'elOffsets': getBoundingClient(bid), + 'scrollTop': window.pageYOffset || document.documentElement.scrollTop } - } + }, + device, + site, + 'imp': [{ + 'banner': { + 'format': (sizes || []).map(s => { return {w: s[0], h: s[1]} }) + }, + 'ext': { + 'prebid': { + 'storedrequest': { + 'id': id + } + } + } + }] } const gdprConsent = bidderRequest && bidderRequest.gdprConsent; @@ -132,6 +156,19 @@ export const spec = { }]; }, }; +function getAdEl(bid) { + // best way I could think of to get El, is by matching adUnitCode to google slots... + const slot = window.googletag && window.googletag.pubads && window.googletag.pubads().getSlots().find(slot => slot.getAdUnitPath() === bid.adUnitCode); + const slotElementId = slot && slot.getSlotElementId(); + if (!slotElementId) return null; + return document.querySelector('#' + slotElementId); +} +function getBoundingClient(bid) { + // console.log(bid) + const el = getAdEl(bid) + if (!el) return {} + return el.getBoundingClientRect(); +} function getPlacementId(bid) { const groupId = getBidIdParameter('group_id', bid.params) @@ -163,4 +200,21 @@ function getTopWindow(curWindow, nesting = 0) { } } +function getSiteObj() { + const refInfo = (getRefererInfo && getRefererInfo()) || {} + + return { + page: refInfo.page, + ref: refInfo.ref, + domain: refInfo.domain + } +} + +function getDeviceObj() { + return { + w: window.innerWidth || window.document.documentElement.clientWidth || window.document.body.clientWidth || 0, + h: window.innerHeight || window.document.documentElement.clientHeight || window.document.body.clientHeight || 0, + } +} + registerBidder(spec); diff --git a/modules/nextMillenniumBidAdapter.md b/modules/nextMillenniumBidAdapter.md index 136f97d94d5..5374accfe35 100644 --- a/modules/nextMillenniumBidAdapter.md +++ b/modules/nextMillenniumBidAdapter.md @@ -2,7 +2,7 @@ ``` Module Name: NextMillennium Bid Adapter Module Type: Bidder Adapter -Maintainer: mihail.ivanchenko@nextmillennium.io +Maintainer: accountmanagers@nextmillennium.io ``` # Description diff --git a/modules/nextrollBidAdapter.js b/modules/nextrollBidAdapter.js index 4e82bc1cbda..0dd4b334f6e 100644 --- a/modules/nextrollBidAdapter.js +++ b/modules/nextrollBidAdapter.js @@ -11,6 +11,7 @@ import { } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {find} from '../src/polyfill.js'; @@ -39,7 +40,10 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - let topLocation = parseUrl(deepAccess(bidderRequest, 'refererInfo.referer')); + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + // TODO: is 'page' the right value here? + let topLocation = parseUrl(deepAccess(bidderRequest, 'refererInfo.page')); return validBidRequests.map((bidRequest) => { return { @@ -65,7 +69,6 @@ export const spec = { } }, - user: _getUser(validBidRequests), site: _getSite(bidRequest, topLocation), seller: _getSeller(bidRequest), device: _getDevice(bidRequest), @@ -186,22 +189,6 @@ function _getNativeAssets(mediaTypeNative) { .filter(asset => asset !== undefined); } -function _getUser(requests) { - const id = deepAccess(requests, '0.userId.nextrollId'); - if (id === undefined) { - return; - } - - return { - ext: { - eid: [{ - 'source': 'nextroll', - id - }] - } - }; -} - function _getFloor(bidRequest) { if (!isFn(bidRequest.getFloor)) { return (bidRequest.params.bidfloor) ? bidRequest.params.bidfloor : null; diff --git a/modules/nextrollIdSystem.js b/modules/nextrollIdSystem.js deleted file mode 100644 index 5a59e216394..00000000000 --- a/modules/nextrollIdSystem.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * This module adds Nextroll ID to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/nextrollIdSystem - * @requires module:modules/userId - */ - -import { deepAccess } from '../src/utils.js'; -import { submodule } from '../src/hook.js'; -import { getStorageManager } from '../src/storageManager.js'; - -const NEXTROLL_ID_LS_KEY = 'dca0.com'; -const KEY_PREFIX = 'AdID:' - -export const storage = getStorageManager(); - -/** @type {Submodule} */ -export const nextrollIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: 'nextrollId', - - /** - * decode the stored id value for passing to bid requests - * @function - * @return {{nextrollId: string} | undefined} - */ - decode(value) { - return value; - }, - - /** - * performs action to obtain id and return a value. - * @function - * @param {SubmoduleConfig} [config] - * @returns {{id: {nextrollId: string} | undefined}} - */ - getId(config) { - const key = KEY_PREFIX + deepAccess(config, 'params.partnerId', 'undefined'); - const dataString = storage.getDataFromLocalStorage(NEXTROLL_ID_LS_KEY) || '{}'; - const data = JSON.parse(dataString); - const idValue = deepAccess(data, `${key}.value`); - - return { id: idValue ? {nextrollId: idValue} : undefined }; - } -}; - -submodule('userId', nextrollIdSubmodule); diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 814a2f55299..ed0c0c66a7a 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -59,7 +59,8 @@ export const spec = { }); const payload = { adUnits, - href: encodeURIComponent(bidderRequest.refererInfo.referer) + // TODO: does the fallback make sense here? + href: encodeURIComponent(bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation) }; if (bidderRequest) { // modules informations (gdpr, ccpa, schain, userId) if (bidderRequest.gdprConsent) { diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index f788093f833..20878b545e4 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -3,6 +3,7 @@ import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const GVLID = 816; const BIDDER_CODE = 'nobid'; @@ -25,20 +26,11 @@ function nobidSetCookie(cname, cvalue, hours) { function nobidGetCookie(cname) { return storage.getCookie(cname); } -function nobidHasPurpose1Consent(bidderRequest) { - let result = true; - if (bidderRequest && bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { - result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); - } - } - return result; -} function nobidBuildRequests(bids, bidderRequest) { var serializeState = function(divIds, siteId, adunits) { var filterAdUnitsByIds = function(divIds, adUnits) { var filtered = []; - if (!divIds || !divIds.length) { + if (!divIds.length) { filtered = adUnits; } else if (adUnits) { var a = []; @@ -88,9 +80,10 @@ function nobidBuildRequests(bids, bidderRequest) { } var topLocation = function(bidderRequest) { var ret = ''; - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - ret = bidderRequest.refererInfo.referer; + if (bidderRequest?.refererInfo?.page) { + ret = bidderRequest.refererInfo.page; } else { + // TODO: does this fallback make sense here? ret = (window.context && window.context.location && window.context.location.href) ? window.context.location.href : document.location.href; } return encodeURIComponent(ret.replace(/\%/g, '')); @@ -152,9 +145,9 @@ function nobidBuildRequests(bids, bidderRequest) { if (cop) state['coppa'] = cop; const eids = getEIDs(deepAccess(bids, '0.userIdAsEids')); if (eids && eids.length > 0) state['eids'] = eids; - if (config && config.getConfig('ortb2')) state['ortb2'] = config.getConfig('ortb2'); + if (bidderRequest && bidderRequest.ortb2) state['ortb2'] = bidderRequest.ortb2; return state; - } + }; function newAdunit(adunitObject, adunits) { var getAdUnit = function(divid, adunits) { for (var i = 0; i < adunits.length; i++) { @@ -386,7 +379,7 @@ export const spec = { const endpoint = buildEndpoint(); let options = {}; - if (!nobidHasPurpose1Consent(bidderRequest)) { + if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { options = { withCredentials: false }; } @@ -445,7 +438,7 @@ export const spec = { type: 'image', url: element }); - }) + }); } return syncs; } else { diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index 661a14ca0ef..7bd1ee8acd9 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -245,7 +245,7 @@ export const novatiqIdSubmodule = { } else { srcId = configParams.sourceid; } - return srcId + return srcId; } }; submodule('userId', novatiqIdSubmodule); diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 746fc1eec03..f0ed3c24a31 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -6,11 +6,12 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { ajax } from '../src/ajax.js' const BIDDER_CODE = 'ogury'; +const GVLID = 31; const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.2.12'; +const ADAPTER_VERSION = '1.2.13'; function getClientWidth() { const documentElementClientWidth = window.top.document.documentElement.clientWidth @@ -198,6 +199,7 @@ function onTimeout(timeoutData) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER], isBidRequestValid, getUserSyncs, @@ -207,6 +209,6 @@ export const spec = { onBidWon, getWindowContext, onTimeout -} +}; registerBidder(spec); diff --git a/modules/oneKeyIdSystem.js b/modules/oneKeyIdSystem.js new file mode 100644 index 00000000000..8d8d594b533 --- /dev/null +++ b/modules/oneKeyIdSystem.js @@ -0,0 +1,78 @@ +/** + * This module adds Onekey data to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/oneKeyIdSystem + * @requires module:modules/userId + */ + +import {submodule} from '../src/hook.js'; +import { logError, logMessage } from '../src/utils.js'; + +// Pre-init OneKey if it has not load yet. +window.OneKey = window.OneKey || {}; +window.OneKey.queue = window.OneKey.queue || []; + +const logPrefix = 'OneKey.Id-Module'; + +/** + * Generate callback that deserializes the result of getIdsAndPreferences. + */ +const onIdsAndPreferencesResult = (callback) => { + return (result) => { + logMessage(logPrefix, `Has got Ids and Prefs with status: `, result); + callback(result.data); + }; +}; + +/** + * Call OneKey once it is loaded for retrieving + * the ids and the preferences. + */ +const getIdsAndPreferences = (callback) => { + logMessage(logPrefix, 'Queue getIdsAndPreferences call'); + // Call OneKey in a queue so that we are sure + // it will be called when fully load and configured + // within the page. + window.OneKey.queue.push(() => { + logMessage(logPrefix, 'Get Ids and Prefs'); + window.OneKey.getIdsAndPreferences() + .then(onIdsAndPreferencesResult(callback)) + .catch(() => { + logError(logPrefix, 'Cannot retrieve the ids and preferences from OneKey.'); + callback(undefined); + }); + }); +}; + +/** @type {Submodule} */ +export const oneKeyIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'oneKeyData', + /** + * decode the stored data value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @returns {(Object|undefined)} + */ + decode(data) { + return { oneKeyData: data }; + }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @param {(Object|undefined)} cacheIdObj + * @returns {IdResponse|undefined} + */ + getId(config) { + return { + callback: getIdsAndPreferences + }; + } +}; + +submodule('userId', oneKeyIdSubmodule); diff --git a/modules/oneKeyIdSystem.md b/modules/oneKeyIdSystem.md new file mode 100644 index 00000000000..36caf382065 --- /dev/null +++ b/modules/oneKeyIdSystem.md @@ -0,0 +1,109 @@ +# OneKey + +The OneKey real-time data module in Prebid has been built so that publishers +can quickly and easily setup the OneKey Addressability Framework. +This module is used along with the oneKeyRtdProvider to pass OneKey data to your partners. +Both modules are required. This module will pass oneKeyData to your partners +while the oneKeyRtdProvider will pass the transmission requests. + +Background information: +- [OneKey-Network/addressability-framework](https://github.com/OneKey-Network/addressability-framework) +- [OneKey-Network/OneKey-implementation](https://github.com/OneKey-Network/OneKey-implementation) + +It can be added to you Prebid.js package with: + +{: .alert.alert-info :} +gulp build –modules=userId,oneKeyIdSystem + +⚠️ This module works in association with a RTD module. See [oneKeyRtdProvider](oneKeyRtdProvider.md). + +#### OneKey Registration + +OneKey is a community based Framework with a decentralized approach. +Go to [onekey.community](https://onekey.community/) for more details. + +#### OneKey Configuration + +{: .table .table-bordered .table-striped } +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module | `"oneKeyData"` | + + +#### OneKey Exemple + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'oneKeyData' + }] + } +}); +``` + +Bidders will receive the data in the following format: + +```json +{ + "identifiers": [{ + "version": "0.1", + "type": "paf_browser_id", + "value": "da135b3a-7d04-44bf-a0af-c4709f10420b", + "source": { + "domain": "crto-poc-1.onekey.network", + "timestamp": 1648836556881, + "signature": "+NF27bBvPM54z103YPExXuS834+ggAQe6JV0jPeGo764vRYiiBl5OmEXlnB7UZgxNe3KBU7rN2jk0SkI4uL0bg==" + } + }], + "preferences": { + "version": "0.1", + "data": { + "use_browsing_for_personalization": true + }, + "source": { + "domain": "cmp.pafdemopublisher.com", + "timestamp": 1648836566468, + "signature": "ipbYhU8IbSFm2tCqAVYI2d5w4DnGF7Xa2AaiZScx2nmBPLfMmIT/FkBYGitR8Mi791DHtcy5MXr4+bs1aeZFqw==" + } + } +} +``` + +If the bidder elects to use pbjs.getUserIdsAsEids() then the format will be: + +```json +"user": { + "ext": { + "eids": [{ + "source": "paf", + "uids": [{ + "id": "da135b3a-7d04-44bf-a0af-c4709f10420b", + "atype": 1, + "ext": { + "version": "0.1", + "type": "paf_browser_id", + "source": { + "domain": "crto-poc-1.onekey.network", + "timestamp": 1648836556881, + "signature": "+NF27bBvPM54z103YPExXuS834+ggAQe6JV0jPeGo764vRYiiBl5OmEXlnB7UZgxNe3KBU7rN2jk0SkI4uL0bg==" + } + } + }], + "ext": { + "preferences": { + "version": "0.1", + "data": { + "use_browsing_for_personalization": true + }, + "source": { + "domain": "cmp.pafdemopublisher.com", + "timestamp": 1648836566468, + "signature": "ipbYhU8IbSFm2tCqAVYI2d5w4DnGF7Xa2AaiZScx2nmBPLfMmIT/FkBYGitR8Mi791DHtcy5MXr4+bs1aeZFqw==" + } + } + } + }] + } +} +``` \ No newline at end of file diff --git a/modules/oneKeyRtdProvider.js b/modules/oneKeyRtdProvider.js new file mode 100644 index 00000000000..27511017676 --- /dev/null +++ b/modules/oneKeyRtdProvider.js @@ -0,0 +1,98 @@ + +import { submodule } from '../src/hook.js'; +import { mergeDeep, logError, logMessage, deepSetValue, generateUUID } from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; + +const SUBMODULE_NAME = 'oneKey'; +const prefixLog = 'OneKey.RTD-module' + +// Pre-init OneKey if it has not load yet. +window.OneKey = window.OneKey || {}; +window.OneKey.queue = window.OneKey.queue || []; + +/** + * Generate the OneKey transmission and include it in the Bid Request. + * + * Modify the AdUnit object for each auction. + * It’s called as part of the requestBids hook. + * https://docs.prebid.org/dev-docs/add-rtd-submodule.html#getbidrequestdata + * + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {Object} rtdConfig + * @param {Object} userConsent + */ +const getTransmissionInBidRequest = (reqBidsConfigObj, done, rtdConfig) => { + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + const transactionIds = adUnits.map(() => generateUUID()); + + logMessage(prefixLog, 'Queue seed generation.'); + window.OneKey.queue.push(() => { + logMessage(prefixLog, 'Generate a seed.'); + window.OneKey.generateSeed(transactionIds) + .then(onGetSeed(reqBidsConfigObj, rtdConfig, adUnits, transactionIds)) + .catch((err) => { logError(SUBMODULE_NAME, err.message); }) + .finally(done); + }); +} + +const onGetSeed = (reqBidsConfigObj, rtdConfig, adUnits, transactionIds) => { + return (seed) => { + if (!seed) { + logMessage(prefixLog, 'No seed generated.'); + return; + } + + logMessage(prefixLog, 'Has retrieved a seed:', seed); + addTransactionIdsToAdUnits(adUnits, transactionIds); + addTransmissionToOrtb2(reqBidsConfigObj, rtdConfig, seed); + }; +}; + +const addTransactionIdsToAdUnits = (adUnits, transactionIds) => { + adUnits.forEach((unit, index) => { + deepSetValue(unit, `ortb2Imp.ext.data.paf.transaction_id`, transactionIds[index]); + }); +}; + +const addTransmissionToOrtb2 = (reqBidsConfigObj, rtdConfig, seed) => { + const okOrtb2 = { + ortb2: { + user: { + ext: { + paf: { + transmission: { + seed + } + } + } + } + } + } + + const shareSeedWithAllBidders = !rtdConfig.params || !rtdConfig.params.bidders; + if (shareSeedWithAllBidders) { + // Change global first party data with OneKey + logMessage(prefixLog, 'set ortb2:', okOrtb2); + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, okOrtb2.ortb2); + } else { + // Change bidder-specific first party data with OneKey + logMessage(prefixLog, `set ortb2 for: ${rtdConfig.params.bidders.join(',')}`, okOrtb2); + rtdConfig.params.bidders.forEach(bidder => { + mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { [bidder]: okOrtb2.ortb2 }); + }); + } +}; + +/** @type {RtdSubmodule} */ +export const oneKeyDataSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: SUBMODULE_NAME, + init: () => true, + getBidRequestData: getTransmissionInBidRequest, +}; + +submodule('realTimeData', oneKeyDataSubmodule); diff --git a/modules/oneKeyRtdProvider.md b/modules/oneKeyRtdProvider.md new file mode 100644 index 00000000000..075e91cafda --- /dev/null +++ b/modules/oneKeyRtdProvider.md @@ -0,0 +1,127 @@ +## OneKey Real-time Data Submodule + +The OneKey real-time data module in Prebid has been built so that publishers +can quickly and easily setup the OneKey Addressability Framework. +This module is used along with the oneKeyIdSystem to pass OneKey data to your partners. +Both modules are required. This module will pass transmission requests to your partners +while the oneKeyIdSystem will pass the oneKeyData. + +Background information: +- [OneKey-Network/addressability-framework](https://github.com/OneKey-Network/addressability-framework) +- [OneKey-Network/OneKey-implementation](https://github.com/OneKey-Network/OneKey-implementation) + +## Implementation for Publishers + +### Integration + +1) Compile the OneKey RTD Provider and the OneKey UserID sub-module into your Prebid build. + +``` +gulp build --modules=rtdModule,oneKeyRtdProvider +``` + +2) Publishers must register OneKey RTD Provider as a Real Time Data provider by using `setConfig` +to load a Prebid Config containing a `realTimeData.dataProviders` array: + +```javascript +pbjs.setConfig({ + ..., + realTimeData: { + auctionDelay: 100, + dataProviders: [ + { + name: 'oneKey', + waitForIt: true + } + ] + } +}); +``` + +3) Configure the OneKey RTD Provider with the bidders that are part of the OneKey community. If there is no bidders specified, the RTD provider +will share OneKey data with all adapters. + +⚠️ This module works in association with a RTD module. See [oneKeyIdSystem](oneKeyIdSystem.md). + +### Parameters + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Always 'oneKey' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | Optional | +| params.bidders | Array | List of bidders to restrict the data to. | Optional | + +## Implementation for Bidders + +### Bidder Requests + +The data will provided to the bidders using the `ortb2` object. +The following is an example of the format of the data: + +```json +"user": { + "ext": { + "paf": { + "transmission": { + "seed": { + "version": "0.1", + "transaction_ids": ["06df6992-691c-4342-bbb0-66d2a005d5b1", "d2cd0aa7-8810-478c-bd15-fb5bfa8138b8"], + "publisher": "cmp.pafdemopublisher.com", + "source": { + "domain": "cmp.pafdemopublisher.com", + "timestamp": 1649712888, + "signature": "turzZlXh9IqD5Rjwh4vWR78pKLrVsmwQrGr6fgw8TPgQVJSC8K3HvkypTV7lm3UaCi+Zzjl+9sd7Hrv87gdI8w==" + } + } + } + } + } +} +``` + +```json +"ortb2Imp": { + "ext": { + "data": { + "paf": { + "transaction_id": "52d23fed-4f50-4c17-b07a-c458143e9d09" + } + } + } +} +``` + +### Bidder Responses + +Bidders who are part of the OneKey Addressability Framework and receive OneKey +transmissions are required to return transmission responses as outlined in +[OneKey-Network/addressability-framework]https://github.com/OneKey-Network/addressability-framework/blob/main/mvp-spec/ad-auction.md). Transmission responses should be appended to bids +along with the releveant content_id using the meta.paf field. The paf-lib will +be responsible for collecting all of the transmission responses. + +Below is an example of setting a transmission response: + +```javascript +bid.meta.paf = { + content_id: "90141190-26fe-497c-acee-4d2b649c2112", + transmission: { + version: "0.1", + contents: [ + { + transaction_id: "f55a401d-e8bb-4de1-a3d2-fa95619393e8", + content_id: "90141190-26fe-497c-acee-4d2b649c2112" + } + ], + status: "success", + details: "", + receiver: "dsp1.com", + source: { + domain: "dsp1.com", + timestamp: 1639589531, + signature: "d01c6e83f14b4f057c2a2a86d320e2454fc0c60df4645518d993b5f40019d24c" + }, + children: [] + } +} +``` diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js deleted file mode 100644 index aeb19e7c32c..00000000000 --- a/modules/oneVideoBidAdapter.js +++ /dev/null @@ -1,408 +0,0 @@ -import { logError, logWarn, parseSizesInput, generateUUID, isFn, logMessage, isPlainObject, isStr, isNumber, isArray } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; - -const BIDDER_CODE = 'oneVideo'; -export const spec = { - code: 'oneVideo', - VERSION: '3.1.2', - ENDPOINT: 'https://ads.adaptv.advertising.com/rtb/openrtb?ext_id=', - E2ETESTENDPOINT: 'https://ads-wc.v.ssp.yahoo.com/rtb/openrtb?ext_id=', - SYNC_ENDPOINT1: 'https://pixel.advertising.com/ups/57304/sync?gdpr=&gdpr_consent=&_origin=0&redir=true', - SYNC_ENDPOINT2: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=adaptv&ttd_tpi=1', - supportedMediaTypes: ['video', 'banner'], - gvlid: 25, - /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - // Bidder code validation - if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { - return false; - } - - // E2E test skip validations - if (bid.params && bid.params.video && bid.params.video.e2etest) { - return true; - } - // MediaTypes Video / Banner validation - if (typeof bid.mediaTypes.video === 'undefined' && typeof bid.mediaTypes.banner === 'undefined') { - logError('Failed validation: adUnit mediaTypes.video OR mediaTypes.banner not declared'); - return false; - }; - - if (bid.mediaTypes.video) { - // Player size validation - if (typeof bid.mediaTypes.video.playerSize === 'undefined') { - if (bid.params.video && (typeof bid.params.video.playerWidth === 'undefined' || typeof bid.params.video.playerHeight === 'undefined')) { - logError('Failed validation: Player size not declared in either mediaTypes.playerSize OR bid.params.video.plauerWidth & bid.params.video.playerHeight.'); - return false; - }; - }; - // Mimes validation - if (typeof bid.mediaTypes.video.mimes === 'undefined') { - if (!bid.params.video || typeof bid.params.video.mimes === 'undefined') { - logError('Failed validation: adUnit mediaTypes.mimes OR params.video.mimes not declared'); - return false; - }; - }; - // Prevend DAP Outstream validation, Banner DAP validation & Multi-Format adUnit support - if (bid.mediaTypes.video.context === 'outstream' && bid.params.video && bid.params.video.display === 1) { - logError('Failed validation: Dynamic Ad Placement cannot be used with context Outstream (params.video.display=1)'); - return false; - }; - }; - - // Publisher Id (Exchange) validation - if (typeof bid.params.pubId === 'undefined') { - logError('Failed validation: Adapter cannot send requests without bid.params.pubId'); - return false; - } - - return true; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @param bidderRequest - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function (bids, bidRequest) { - let consentData = bidRequest ? bidRequest.gdprConsent : null; - - return bids.map(bid => { - let url = spec.ENDPOINT - let pubId = bid.params.pubId; - if (bid.params.video.e2etest) { - url = spec.E2ETESTENDPOINT; - pubId = 'HBExchange'; - } - return { - method: 'POST', - /** removing adding local protocal since we - * can get cookie data only if we call with https. */ - url: url + pubId, - data: getRequestData(bid, consentData, bidRequest), - bidRequest: bid - } - }) - }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function(response, {bidRequest}) { - let bid; - let size; - let bidResponse; - try { - response = response.body; - bid = response.seatbid[0].bid[0]; - } catch (e) { - response = null; - } - if (!response || !bid || (!bid.adm && !bid.nurl) || !bid.price) { - logWarn(`No valid bids from ${spec.code} bidder`); - return []; - } - size = getSize(bidRequest.sizes); - bidResponse = { - requestId: bidRequest.bidId, - bidderCode: spec.code, - cpm: bid.price, - creativeId: bid.crid, - width: size.width, - height: size.height, - currency: response.cur, - ttl: (bidRequest.params.video.ttl > 0 && bidRequest.params.video.ttl <= 3600) ? bidRequest.params.video.ttl : 300, - netRevenue: true, - adUnitCode: bidRequest.adUnitCode, - meta: { - advertiserDomains: bid.adomain - } - }; - - bidResponse.mediaType = (bidRequest.mediaTypes.banner) ? 'banner' : 'video' - - if (bid.nurl) { - bidResponse.vastUrl = bid.nurl; - } else if (bid.adm && bidRequest.params.video.display === 1) { - bidResponse.ad = bid.adm - } else if (bid.adm) { - bidResponse.vastXml = bid.adm; - } - if (bidRequest.mediaTypes.video) { - bidResponse.renderer = (bidRequest.mediaTypes.video.context === 'outstream') ? newRenderer(bidRequest, bidResponse) : undefined; - } - - return bidResponse; - }, - /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ - getUserSyncs: function(syncOptions, responses, consentData = {}) { - let { - gdprApplies, - consentString = '' - } = consentData; - - if (syncOptions.pixelEnabled) { - return [{ - type: 'image', - url: spec.SYNC_ENDPOINT1 - }, - { - type: 'image', - url: `https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0` + encodeURI(`&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}`) - }, - { - type: 'image', - url: spec.SYNC_ENDPOINT2 - }]; - } - } -}; - -function getSize(sizes) { - let parsedSizes = parseSizesInput(sizes); - let [ width, height ] = parsedSizes.length ? parsedSizes[0].split('x') : []; - return { - width: parseInt(width, 10) || undefined, - height: parseInt(height, 10) || undefined - }; -} - -function isConsentRequired(consentData) { - return !!(consentData && consentData.gdprApplies); -} - -function getRequestData(bid, consentData, bidRequest) { - let loc = bidRequest.refererInfo.referer; - let page = (bid.params.site && bid.params.site.page) ? (bid.params.site.page) : (loc.href); - let ref = (bid.params.site && bid.params.site.referrer) ? bid.params.site.referrer : bidRequest.refererInfo.referer; - let getFloorRequestObject = { - currency: bid.params.cur || 'USD', - mediaType: 'video', - size: '*' - }; - let bidData = { - id: generateUUID(), - at: 2, - imp: [{ - id: '1', - secure: isSecure(), - ext: { - hb: 1, - prebidver: '$prebid.version$', - adapterver: spec.VERSION, - } - }], - site: { - page: page, - ref: ref - }, - device: { - ua: navigator.userAgent - }, - tmax: 200 - }; - - if (bid.params.video.display == undefined || bid.params.video.display != 1) { - bidData.imp[0].video = { - linearity: 1 - }; - if (bid.params.video.playerWidth && bid.params.video.playerHeight) { - bidData.imp[0].video.w = bid.params.video.playerWidth; - bidData.imp[0].video.h = bid.params.video.playerHeight; - } else { - const playerSize = getSize(bid.mediaTypes.video.playerSize); - bidData.imp[0].video.w = playerSize.width; - bidData.imp[0].video.h = playerSize.height; - }; - if (bid.params.video.mimes) { - bidData.imp[0].video.mimes = bid.params.video.mimes; - } else { - bidData.imp[0].video.mimes = bid.mediaTypes.video.mimes; - }; - if (bid.mediaTypes.video.maxbitrate || bid.params.video.maxbitrate) { - bidData.imp[0].video.maxbitrate = bid.params.video.maxbitrate || bid.mediaTypes.video.maxbitrate; - } - if (bid.mediaTypes.video.maxduration || bid.params.video.maxduration) { - bidData.imp[0].video.maxduration = bid.params.video.maxduration || bid.mediaTypes.video.maxduration; - } - if (bid.mediaTypes.video.minduration || bid.params.video.minduration) { - bidData.imp[0].video.minduration = bid.params.video.minduration || bid.mediaTypes.video.minduration; - } - if (bid.mediaTypes.video.api || bid.params.video.api) { - bidData.imp[0].video.api = bid.params.video.api || bid.mediaTypes.video.api; - } - if (bid.mediaTypes.video.delivery || bid.params.video.delivery) { - bidData.imp[0].video.delivery = bid.params.video.delivery || bid.mediaTypes.video.delivery; - } - if (bid.mediaTypes.video.position || bid.params.video.position) { - bidData.imp[0].video.pos = bid.params.video.position || bid.mediaTypes.video.position; - } - if (bid.mediaTypes.video.playbackmethod || bid.params.video.playbackmethod) { - bidData.imp[0].video.playbackmethod = bid.params.video.playbackmethod || bid.mediaTypes.video.playbackmethod; - } - if (bid.mediaTypes.video.placement || bid.params.video.placement) { - bidData.imp[0].video.placement = bid.params.video.placement || bid.mediaTypes.video.placement; - } - if (bid.params.video.rewarded) { - bidData.imp[0].ext.rewarded = bid.params.video.rewarded - } - if (bid.mediaTypes.video.linearity || bid.params.video.linearity) { - bidData.imp[0].video.linearity = bid.params.video.linearity || bid.mediaTypes.video.linearity || 1; - } - if (bid.mediaTypes.video.protocols || bid.params.video.protocols) { - bidData.imp[0].video.protocols = bid.params.video.protocols || bid.mediaTypes.video.protocols || [2, 5]; - } - } else if (bid.params.video.display == 1) { - getFloorRequestObject.mediaType = 'banner'; - bidData.imp[0].banner = { - mimes: bid.params.video.mimes, - w: bid.params.video.playerWidth, - h: bid.params.video.playerHeight, - pos: bid.params.video.position, - }; - if (bid.params.video.placement) { - bidData.imp[0].banner.placement = bid.params.video.placement - } - if (bid.params.video.maxduration) { - bidData.imp[0].banner.ext = bidData.imp[0].banner.ext || {} - bidData.imp[0].banner.ext.maxduration = bid.params.video.maxduration - } - if (bid.params.video.minduration) { - bidData.imp[0].banner.ext = bidData.imp[0].banner.ext || {} - bidData.imp[0].banner.ext.minduration = bid.params.video.minduration - } - } - - if (isFn(bid.getFloor)) { - let floorData = bid.getFloor(getFloorRequestObject); - bidData.imp[0].bidfloor = floorData.floor; - bidData.cur = floorData.currency; - } else { - bidData.imp[0].bidfloor = bid.params.bidfloor; - }; - - if (bid.params.video.inventoryid) { - bidData.imp[0].ext.inventoryid = bid.params.video.inventoryid - } - if (bid.params.video.sid) { - bidData.source = { - ext: { - schain: { - complete: 1, - nodes: [{ - sid: bid.params.video.sid, - rid: bidData.id, - }] - } - } - } - if (bid.params.video.hp == 1) { - bidData.source.ext.schain.nodes[0].hp = bid.params.video.hp; - } - } else if (bid.schain) { - bidData.source = { - ext: { - schain: bid.schain - } - } - bidData.source.ext.schain.nodes[0].rid = bidData.id; - } - if (bid.params.site && bid.params.site.id) { - bidData.site.id = bid.params.site.id - } - if (isConsentRequired(consentData) || (bidRequest && bidRequest.uspConsent)) { - bidData.regs = { - ext: {} - }; - if (isConsentRequired(consentData)) { - bidData.regs.ext.gdpr = 1 - } - - if (consentData && consentData.consentString) { - bidData.user = { - ext: { - consent: consentData.consentString - } - }; - } - // ccpa support - if (bidRequest && bidRequest.uspConsent) { - bidData.regs.ext.us_privacy = bidRequest.uspConsent - } - } - if (bid.params.video.e2etest) { - logMessage('E2E test mode enabled: \n The following parameters are being overridden by e2etest mode:\n* bidfloor:null\n* width:300\n* height:250\n* mimes: video/mp4, application/javascript\n* api:2\n* site.page/ref: verizonmedia.com\n* tmax:1000'); - bidData.imp[0].bidfloor = null; - bidData.imp[0].video.w = 300; - bidData.imp[0].video.h = 250; - bidData.imp[0].video.mimes = ['video/mp4', 'application/javascript']; - bidData.imp[0].video.api = [2]; - bidData.site.page = 'https://verizonmedia.com'; - bidData.site.ref = 'https://verizonmedia.com'; - bidData.tmax = 1000; - } - if (bid.params.video.custom && isPlainObject(bid.params.video.custom)) { - bidData.imp[0].ext.custom = {}; - for (const key in bid.params.video.custom) { - if (isStr(bid.params.video.custom[key]) || isNumber(bid.params.video.custom[key])) { - bidData.imp[0].ext.custom[key] = bid.params.video.custom[key]; - } - } - } - if (bid.params.video.content && isPlainObject(bid.params.video.content)) { - bidData.site.content = {}; - const contentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language']; - const contentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; - const contentArrayKeys = ['cat']; - const contentObjectKeys = ['ext']; - for (const contentKey in bid.params.video.content) { - if ( - (contentStringKeys.indexOf(contentKey) > -1 && isStr(bid.params.video.content[contentKey])) || - (contentNumberkeys.indexOf(contentKey) > -1 && isNumber(bid.params.video.content[contentKey])) || - (contentObjectKeys.indexOf(contentKey) > -1 && isPlainObject(bid.params.video.content[contentKey])) || - (contentArrayKeys.indexOf(contentKey) > -1 && isArray(bid.params.video.content[contentKey]) && - bid.params.video.content[contentKey].every(catStr => isStr(catStr)))) { - bidData.site.content[contentKey] = bid.params.video.content[contentKey]; - } else { - logMessage('oneVideo bid adapter validation error: ', contentKey, ' is either not supported is OpenRTB V2.5 or value is undefined'); - } - } - } - return bidData; -} - -function isSecure() { - return document.location.protocol === 'https:'; -} -/** - * Create oneVideo renderer - * @returns {*} - */ -function newRenderer(bidRequest, bid) { - if (!bidRequest.renderer) { - bidRequest.renderer = {}; - bidRequest.renderer.url = 'https://cdn.vidible.tv/prod/hb-outstream-renderer/renderer.js'; - bidRequest.renderer.render = function(bid) { - setTimeout(function() { - // eslint-disable-next-line no-undef - o2PlayerRender(bid); - }, 700) - }; - } -} - -registerBidder(spec); diff --git a/modules/oneVideoBidAdapter.md b/modules/oneVideoBidAdapter.md deleted file mode 100644 index 149a4b20e2f..00000000000 --- a/modules/oneVideoBidAdapter.md +++ /dev/null @@ -1,442 +0,0 @@ -# Overview - -**Module Name**: One Video Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: deepthi.neeladri.sravana@verizonmedia.com - adam.browning@verizonmedia.com - -# Description -Connects to Verizon Media's Video SSP (AKA ONE Video / Adap.tv) demand source to fetch bids. -# Prebid.js V5.0 Support -The oneVideo adapter now reads `mediaTypes.video` for mandatory parameters such as `playerSize` & `mimes`. -Note: You can use the `bid.params.video` object to specify explicit overrides for whatever is declared in `mediaTypes.video`. -Important: You must pass `bid.params.video = {}` as bare minimum for the adapter to work. -# Integration Examples: - -## Instream Video adUnit using mediaTypes.video -*Note:* By default, the adapter will read the mandatory parameters from mediaTypes.video. -*Note:* The Video SSP ad server will respond with an VAST XML to load into your defined player. -``` - var adUnits = [ - { - code: 'video1', - mediaTypes: { - video: { - context: 'instream', - playerSize: [480, 640], - mimes: ['video/mp4', 'application/javascript'], - protocols: [2,5], - api: [2], - position: 1, - delivery: [2], - minduration: 10, - maxduration: 30, - placement: 1, - playbackmethod: [1,5], - protocols: [2,5], - api: [2], - } - }, - bids: [ - { - bidder: 'oneVideo', - params: { - video: { - sid: YOUR_VSSP_ORG_ID, - hp: 1, - rewarded: 1, - inventoryid: 123, - ttl: 300, - custom: { - key1: "value1", - key2: 123345 - } - }, - bidfloor: 0.5, - site: { - id: 1, - page: 'https://verizonmedia.com', - referrer: 'https://verizonmedia.com' - }, - pubId: 'HBExchange' - } - } - ] - } - ] -``` -## Instream Video adUnit using params.video overrides -*Note:* If the mandatory parameters are not specified in mediaTypes.video the adapter will read check to see if overrides are set in params.video. Decalring values using params.video will always override the settings in mediaTypes.video. -*Note:* The Video SSP ad server will respond with an VAST XML to load into your defined player. -``` - var adUnits = [ - { - code: 'video1', - mediaTypes: { - video: { - context: 'instream', - } - }, - bids: [ - { - bidder: 'oneVideo', - params: { - video: { - playerWidth: 640, - playerHeight: 480, - mimes: ['video/mp4', 'application/javascript'], - protocols: [2,5], - api: [2], - position: 1, - delivery: [2], - minduration: 10, - maxduration: 30, - placement: 1, - playbackmethod: [1,5], - protocols: [2,5], - api: [2], - sid: YOUR_VSSP_ORG_ID, - hp: 1, - rewarded: 1, - inventoryid: 123, - ttl: 300, - custom: { - key1: "value1", - key2: 123345 - } - }, - bidfloor: 0.5, - site: { - id: 1, - page: 'https://verizonmedia.com', - referrer: 'https://verizonmedia.com' - }, - pubId: 'HBExchange' - } - } - ] - } - ] -``` -## Outstream Video adUnit example & parameters -*Note:* The Video SSP ad server will load it's own Outstream Renderer (player) as a fallback if no player is defined on the publisher page. The Outstream player will inject into the div id that has an identical adUnit code. -``` - var adUnits = [ - { - code: 'video1', - mediaTypes: { - video: { - context: 'outstream', - playerSize: [480, 640], - mimes: ['video/mp4', 'application/javascript'], - protocols: [2,5], - api: [2], - position: 1, - delivery: [2], - minduration: 10, - maxduration: 30, - placement: 1, - playbackmethod: [1,5], - protocols: [2,5], - api: [2], - - } - }, - bids: [ - { - bidder: 'oneVideo', - params: { - video: { - sid: YOUR_VSSP_ORG_ID, - hp: 1, - rewarded: 1, - ttl: 250 - }, - bidfloor: 0.5, - site: { - id: 1, - page: 'https://verizonmedia.com', - referrer: 'https://verizonmedia.com' - }, - pubId: 'HBExchange' - } - } - ] - } - ] -``` - -## S2S / Video: Dynamic Ad Placement (DAP) adUnit example & parameters -*Note:* The Video SSP ad server will respond with HTML embed tag to be injected into an iFrame you create. -``` - var adUnits = [ - { - code: 'video1', - mediaTypes: { - video: { - context: "instream", - playerSize: [480, 640], - mimes: ['video/mp4', 'application/javascript'], - } - }, - bids: [ - { - bidder: 'oneVideo', - params: { - video: { - ttl: 250 - }, - bidfloor: 0.5, - site: { - id: 1, - page: 'https://verizonmedia.com', - referrer: 'https://verizonmedia.com' - }, - pubId: 'HBExchangeDAP' - } - } - ] - } -] -``` -## Prebid.js / Banner: Dynamic Ad Placement (DAP) adUnit example & parameters -*Note:* The Video SSP ad server will respond with HTML embed tag to be injected into an iFrame created by Google Ad Manager (GAM). -``` - var adUnits = [ - { - code: 'banner-1', - mediaTypes: { - banner: { - sizes: [300, 250] - } - }, - bids: [ - { - bidder: 'oneVideo', - params: { - video: { - playerWidth: 300, - playerHeight: 250, - mimes: ['video/mp4', 'application/javascript'], - display: 1 - }, - bidfloor: 0.5, - site: { - id: 1, - page: 'https://verizonmedia.com', - referrer: 'https://verizonmedia.com' - }, - pubId: 'HBExchangeDAP' - } - } - ] - } -] -``` - -# End 2 End Testing Mode -By passing bid.params.video.e2etest = true you will be able to receive a test creative when connecting via VPN location U.S West Coast. This will allow you to trubleshoot how your player/ad-server parses and uses the VAST XML response. -This automatically sets default values for the outbound bid-request to respond from our test exchange. -No need to override the site/ref urls or change your pubId -``` -var adUnits = [ - { - code: 'video-1', - mediaTypes: { - video: { - context: "instream", - playerSize: [480, 640] - mimes: ['video/mp4', 'application/javascript'], - } - }, - bids: [ - { - bidder: 'oneVideo', - params: { - video: { - e2etest: true - } - } - } - ] - } -] -``` - -# Supply Chain Object Support -The oneVideoBidAdapter supports 2 methods for passing/creating an schain object. -1. By passing your Video SSP Org ID in the bid.video.params.sid - The adapter will create a new schain object and our ad-server will fill in the data for you. -2. Using the Prebid Supply Chain Object Module - The adapter will capture the schain object -*Note:* You cannot pass both schain object and bid.video.params.sid together. Option 1 will always be the default. - -## Create new schain using bid.video.params.sid -sid = your Video SSP Organization ID. -This is for direct publishers only. -``` -var adUnits = [ - { - code: 'video1', - mediaTypes: { - video: { - context: 'instream', - playerSize: [480, 640], - mimes: ['video/mp4', 'application/javascript'], - protocols: [2,5], - api: [2], - } - }, - bids: [ - { - bidder: 'oneVideo', - params: { - video: { - sid: 123456 - }, - bidfloor: 0.5, - site: { - id: 1, - page: 'https://verizonmedia.com', - referrer: 'https://verizonmedia.com' - }, - pubId: 'HBExchange' - } - } - ] - } - ] -``` - -## Pass global schain using pbjs.setConfig(SCHAIN_OBJECT) -For both Authorized resellers and direct publishers. -``` -pbjs.setConfig({ - "schain": { - "validation": "strict", - "config": { - "ver": "1.0", - "complete": 1, - "nodes": [{ - "asi": "some-platform.com", - "sid": "111111", - "hp": 1 - }] - } - } -}); - -var adUnits = [ - { - code: 'video1', - mediaTypes: { - video: { - context: 'instream', - playerSize: [480, 640], - mimes: ['video/mp4', 'application/javascript'], - protocols: [2,5], - api: [2], - } - }, - bids: [ - { - bidder: 'oneVideo', - params: { - video: { - ttl: 250 - }, - bidfloor: 0.5, - site: { - id: 1, - page: 'https://verizonmedia.com', - referrer: 'https://verizonmedia.com' - }, - pubId: 'HBExchange' - } - } - ] - } - ] -``` -# Content Object Support -The oneVideoBidAdapter supports passing of OpenRTB V2.5 Content Object. - -``` -const adUnits = [{ - code: 'video1', - mediaTypes: { - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4', 'application/javascript'], - protocols: [2, 5], - api: [1, 2], - } - }, - bids: [{ - bidder: 'oneVideo', - params: { - video: { - ttl: 300, - content: { - id: "1234", - title: "Title", - series: "Series", - season: "Season", - episode: 1 - cat: [ - "IAB1", - "IAB1-1", - "IAB1-2", - "IAB2", - "IAB2-1" - ], - genre: "Genre", - contentrating: "C-Rating", - language: "EN", - prodq: 1, - context: 1, - livestream: 0, - len: 360, - ext: { - network: "ext-network", - channel: "ext-channel" - } - } - }, - bidfloor: 0.5, - pubId: 'HBExchange' - } - } - }] - }] -``` - - -# TTL Support -The oneVideoBidAdapter supports passing of "Time To Live" (ttl) that indicates to prebid chache for how long to keep the chaced winning bid alive. -Value is Number in seconds -You can enter any number between 1 - 3600 (seconds) -``` -const adUnits = [{ - code: 'video1', - mediaTypes: { - video: { - context: 'outstream', - playerSize: [640, 480], - mimes: ['video/mp4', 'application/javascript'], - protocols: [2, 5], - api: [1, 2], - } - }, - bids: [{ - bidder: 'oneVideo', - params: { - video: { - ttl: 300 - }, - bidfloor: 0.5, - pubId: 'HBExchange' - } - }] - }] -``` - diff --git a/modules/oneplanetonlyBidAdapter.md b/modules/oneplanetonlyBidAdapter.md deleted file mode 100644 index 973adb33efd..00000000000 --- a/modules/oneplanetonlyBidAdapter.md +++ /dev/null @@ -1,50 +0,0 @@ -# Overview - -``` -Module Name: OnePlanetOnly Bidder Adapter -Module Type: Bidder Adapter -Maintainer: vitaly@oneplanetonly.com -``` - -# Description - -Module that connects to OnePlanetOnly's demand sources - -# Test Parameters -``` - var adUnits = [ - { - code: 'desktop-banner-ad-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - } - }, - bids: [ - { - bidder: 'oneplanetonly', - params: { - siteId: '5', - adUnitId: '5-4587544' - } - } - ] - },{ - code: 'mobile-banner-ad-div', - mediaTypes: { - banner: { - sizes: [[320, 50], [320, 100]], - } - }, - bids: [ - { - bidder: "oneplanetonly", - params: { - siteId: '5', - adUnitId: '5-81037880' - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 89c614dba23..40ef6d596be 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -1,13 +1,12 @@ 'use strict'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {INSTREAM, OUTSTREAM} from '../src/video.js'; -import {Renderer} from '../src/Renderer.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { INSTREAM, OUTSTREAM } from '../src/video.js'; +import { Renderer } from '../src/Renderer.js'; import {find} from '../src/polyfill.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {createEidsArray} from './userId/eids.js'; -import {deepClone} from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepClone, logError, deepAccess } from '../src/utils.js'; const ENDPOINT = 'https://onetag-sys.com/prebid-request'; const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; @@ -65,8 +64,11 @@ function buildRequests(validBidRequests, bidderRequest) { if (bidderRequest && bidderRequest.uspConsent) { payload.usPrivacy = bidderRequest.uspConsent; } - if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].userId) { - payload.userId = createEidsArray(validBidRequests[0].userId); + if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].userIdAsEids) { + payload.userId = validBidRequests[0].userIdAsEids; + } + if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].schain && isSchainValid(validBidRequests[0].schain)) { + payload.schain = validBidRequests[0].schain; } try { if (storage.hasLocalStorage()) { @@ -245,6 +247,7 @@ function requestsToBids(bidRequests) { // Other params videoObj['mediaTypeInfo'] = deepClone(bidRequest.mediaTypes.video); videoObj['type'] = VIDEO; + videoObj['priceFloors'] = getBidFloor(bidRequest, VIDEO, videoObj['playerSize']); return videoObj; }); const bannerBidRequests = bidRequests.filter(bidRequest => isValid(BANNER, bidRequest)).map(bidRequest => { @@ -253,6 +256,7 @@ function requestsToBids(bidRequests) { bannerObj['sizes'] = parseSizes(bidRequest); bannerObj['type'] = BANNER; bannerObj['mediaTypeInfo'] = deepClone(bidRequest.mediaTypes.banner); + bannerObj['priceFloors'] = getBidFloor(bidRequest, BANNER, bannerObj['sizes']); return bannerObj; }); return videoBidRequests.concat(bannerBidRequests); @@ -265,6 +269,7 @@ function setGeneralInfo(bidRequest) { this['bidderRequestId'] = bidRequest.bidderRequestId; this['auctionId'] = bidRequest.auctionId; this['transactionId'] = bidRequest.transactionId; + this['gpid'] = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); this['pubId'] = params.pubId; this['ext'] = params.ext; if (params.pubClick) { @@ -373,6 +378,37 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { return syncs; } +function getBidFloor(bidRequest, mediaType, sizes) { + const priceFloors = []; + if (typeof bidRequest.getFloor === 'function') { + sizes.forEach(size => { + const floor = bidRequest.getFloor({ + currency: 'EUR', + mediaType: mediaType || '*', + size: [size.width, size.height] + }); + floor.size = deepClone(size); + if (!floor.floor) { floor.floor = null; } + priceFloors.push(floor); + }); + } + return priceFloors; +} + +export function isSchainValid(schain) { + let isValid = false; + const requiredFields = ['asi', 'sid', 'hp']; + if (!schain || !schain.nodes) return isValid; + isValid = schain.nodes.reduce((status, node) => { + if (!status) return status; + return requiredFields.every(field => node.hasOwnProperty(field)); + }, true); + if (!isValid) { + logError('OneTag: required schain params missing'); + } + return isValid; +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index 25b0f1a5934..d99455f3f73 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -1,7 +1,19 @@ -import { getBidIdParameter, _each, isArray, getWindowTop, getUniqueIdentifierStr, parseUrl, logError, logWarn, createTrackPixelHtml, getWindowSelf, isFn, isPlainObject } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { + _each, + createTrackPixelHtml, + getBidIdParameter, + getUniqueIdentifierStr, + getWindowSelf, + getWindowTop, + isArray, + isFn, + isPlainObject, + logError, + logWarn +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; const BIDDER_CODE = 'onomagic'; const URL = 'https://bidder.onomagic.com/hb'; @@ -19,7 +31,7 @@ function buildRequests(bidReqs, bidderRequest) { try { let referrer = ''; if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.referer; + referrer = bidderRequest.refererInfo.page; } const onomagicImps = []; const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); @@ -56,7 +68,8 @@ function buildRequests(bidReqs, bidderRequest) { id: getUniqueIdentifierStr(), imp: onomagicImps, site: { - domain: parseUrl(referrer).host, + // TODO: does the fallback make sense here? + domain: bidderRequest?.refererInfo?.domain || window.location.host, page: referrer, publisher: { id: publisherId diff --git a/modules/ooloAnalyticsAdapter.js b/modules/ooloAnalyticsAdapter.js index 398459d604d..9bc140f0536 100644 --- a/modules/ooloAnalyticsAdapter.js +++ b/modules/ooloAnalyticsAdapter.js @@ -1,5 +1,6 @@ -import { _each, deepClone, pick, deepSetValue, getOrigin, logError, logInfo } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js' +import { _each, deepClone, pick, deepSetValue, logError, logInfo } from '../src/utils.js'; +import { getOrigin } from '../libraries/getOrigin/index.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js' import adapterManager from '../src/adapterManager.js' import CONSTANTS from '../src/constants.json' import { ajax } from '../src/ajax.js' diff --git a/modules/open8BidAdapter.js b/modules/open8BidAdapter.js index 7fa97235525..5fa1dd0a143 100644 --- a/modules/open8BidAdapter.js +++ b/modules/open8BidAdapter.js @@ -68,7 +68,7 @@ export const spec = { meta: { advertiserDomains: ad.adomain || [] } - } + }; if (ad.adType === AD_TYPE.VIDEO) { const videoAd = bidderResponse.ad.video; diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index f515eb14011..296bfc682f1 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -126,7 +126,8 @@ function parseRTBResponse(serverResponse, adapterRequest) { function bidToTag(bidRequests, adapterRequest) { // start publisher env const tag = { - Domain: deepAccess(adapterRequest, 'refererInfo.referer') + // TODO: is 'page' the right value here? + Domain: deepAccess(adapterRequest, 'refererInfo.page') }; if (config.getConfig('coppa') === true) { tag.Coppa = 1; @@ -148,7 +149,7 @@ function bidToTag(bidRequests, adapterRequest) { tag.UserEids = deepAccess(bidRequests[0], 'userIdAsEids'); } // end publisher env - const bids = [] + const bids = []; for (let i = 0, length = bidRequests.length; i < length; i++) { const bid = prepareBidRequests(bidRequests[i]); diff --git a/modules/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js index 89140c0aacd..3c5517223af 100644 --- a/modules/openxAnalyticsAdapter.js +++ b/modules/openxAnalyticsAdapter.js @@ -1,128 +1,19 @@ import { - _each, - _map, - deepAccess, - flatten, - getWindowLocation, - isEmpty, - logError, - logInfo, - logMessage, - logWarn, - parseQS, - parseSizesInput, - uniques + logWarn } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import {ajax} from '../src/ajax.js'; -import {find, includes} from '../src/polyfill.js'; -export const AUCTION_STATES = { - INIT: 'initialized', // auction has initialized - ENDED: 'ended', // all auction requests have been accounted for - COMPLETED: 'completed' // all slots have rendered -}; - -const ADAPTER_VERSION = '0.1'; -const SCHEMA_VERSION = '0.1'; - -const AUCTION_END_WAIT_TIME = 1000; -const URL_PARAM = ''; -const ANALYTICS_TYPE = 'endpoint'; -const ENDPOINT = 'https://prebid.openx.net/ox/analytics/'; - -// Event Types -const { - EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, AUCTION_END, BID_WON } -} = CONSTANTS; -const SLOT_LOADED = 'slotOnload'; - -const UTM_TAGS = [ - 'utm_campaign', - 'utm_source', - 'utm_medium', - 'utm_term', - 'utm_content' -]; -const UTM_TO_CAMPAIGN_PROPERTIES = { - 'utm_campaign': 'name', - 'utm_source': 'source', - 'utm_medium': 'medium', - 'utm_term': 'term', - 'utm_content': 'content' -}; - -/** - * @typedef {Object} OxAnalyticsConfig - * @property {string} orgId - * @property {string} publisherPlatformId - * @property {number} publisherAccountId - * @property {string} configId - * @property {string} optimizerConfig - * @property {number} sampling - * @property {Object} campaign - * @property {number} payloadWaitTime - * @property {number} payloadWaitTimePadding - * @property {Array} adUnits - */ - -/** - * @type {OxAnalyticsConfig} - */ -const DEFAULT_ANALYTICS_CONFIG = { - orgId: void (0), - publisherPlatformId: void (0), - publisherAccountId: void (0), - sampling: 0.05, // default sampling rate of 5% - testCode: 'default', - campaign: {}, - adUnits: [], - payloadWaitTime: AUCTION_END_WAIT_TIME, - payloadWaitTimePadding: 2000 -}; - -// Initialization -/** - * @type {OxAnalyticsConfig} - */ -let analyticsConfig; -let auctionMap = {}; -let auctionOrder = 1; // tracks the number of auctions ran on the page - -let googletag = window.googletag || {}; -googletag.cmd = googletag.cmd || []; - -let openxAdapter = Object.assign(adapter({ urlParam: URL_PARAM, analyticsType: ANALYTICS_TYPE })); +let openxAdapter = Object.assign(adapter({ urlParam: '', analyticsType: 'endpoint' })); openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics; openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) { - if (isValidConfig(adapterConfig)) { - analyticsConfig = {...DEFAULT_ANALYTICS_CONFIG, ...adapterConfig.options}; - - // campaign properties defined by config will override utm query parameters - analyticsConfig.campaign = {...buildCampaignFromUtmCodes(), ...analyticsConfig.campaign}; - - logInfo('OpenX Analytics enabled with config', analyticsConfig); + logWarn('OpenX Analytics has been deprecated, this adapter will be removed in Prebid 8'); - // override track method with v2 handlers - openxAdapter.track = prebidAnalyticsEventHandler; + openxAdapter.track = prebidAnalyticsEventHandler; - googletag.cmd.push(function () { - let pubads = googletag.pubads(); - - if (pubads.addEventListener) { - pubads.addEventListener(SLOT_LOADED, args => { - openxAdapter.track({eventType: SLOT_LOADED, args}); - logInfo('OX: SlotOnLoad event triggered'); - }); - } - }); - - openxAdapter.originEnableAnalytics(adapterConfig); - } + openxAdapter.originEnableAnalytics(adapterConfig); }; adapterManager.registerAnalyticsAdapter({ @@ -132,635 +23,5 @@ adapterManager.registerAnalyticsAdapter({ export default openxAdapter; -/** - * Test Helper Functions - */ - -// reset the cache for unit tests -openxAdapter.reset = function() { - auctionMap = {}; - auctionOrder = 1; -}; - -/** - * Private Functions - */ - -function isValidConfig({options: analyticsOptions}) { - let hasOrgId = analyticsOptions && analyticsOptions.orgId !== void (0); - - const fieldValidations = [ - // tuple of property, type, required - ['orgId', 'string', hasOrgId], - ['publisherPlatformId', 'string', !hasOrgId], - ['publisherAccountId', 'number', !hasOrgId], - ['configId', 'string', false], - ['optimizerConfig', 'string', false], - ['sampling', 'number', false], - ['adIdKey', 'string', false], - ['payloadWaitTime', 'number', false], - ['payloadWaitTimePadding', 'number', false], - ]; - - let failedValidation = find(fieldValidations, ([property, type, required]) => { - // if required, the property has to exist - // if property exists, type check value - return (required && !analyticsOptions.hasOwnProperty(property)) || - /* eslint-disable valid-typeof */ - (analyticsOptions.hasOwnProperty(property) && typeof analyticsOptions[property] !== type); - }); - if (failedValidation) { - let [property, type, required] = failedValidation; - - if (required) { - logError(`OpenXAnalyticsAdapter: Expected '${property}' to exist and of type '${type}'`); - } else { - logError(`OpenXAnalyticsAdapter: Expected '${property}' to be type '${type}'`); - } - } - - return !failedValidation; -} - -function buildCampaignFromUtmCodes() { - const location = getWindowLocation(); - const queryParams = parseQS(location && location.search); - let campaign = {}; - - UTM_TAGS.forEach(function(utmKey) { - let utmValue = queryParams[utmKey]; - if (utmValue) { - let key = UTM_TO_CAMPAIGN_PROPERTIES[utmKey]; - campaign[key] = decodeURIComponent(utmValue); - } - }); - return campaign; -} - -function detectMob() { - if ( - navigator.userAgent.match(/Android/i) || - navigator.userAgent.match(/webOS/i) || - navigator.userAgent.match(/iPhone/i) || - navigator.userAgent.match(/iPad/i) || - navigator.userAgent.match(/iPod/i) || - navigator.userAgent.match(/BlackBerry/i) || - navigator.userAgent.match(/Windows Phone/i) - ) { - return true; - } else { - return false; - } -} - -function detectOS() { - if (navigator.userAgent.indexOf('Android') != -1) return 'Android'; - if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS'; - if (navigator.userAgent.indexOf('Win') != -1) return 'Windows'; - if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh'; - if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux'; - if (navigator.appVersion.indexOf('X11') != -1) return 'Unix'; - return 'Others'; -} - -function detectBrowser() { - var isChrome = - /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); - var isCriOS = navigator.userAgent.match('CriOS'); - var isSafari = - /Safari/.test(navigator.userAgent) && - /Apple Computer/.test(navigator.vendor); - var isFirefox = /Firefox/.test(navigator.userAgent); - var isIE = - /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent); - var isEdge = /Edge/.test(navigator.userAgent); - if (isIE) return 'Internet Explorer'; - if (isEdge) return 'Microsoft Edge'; - if (isCriOS) return 'Chrome'; - if (isSafari) return 'Safari'; - if (isFirefox) return 'Firefox'; - if (isChrome) return 'Chrome'; - return 'Others'; -} - function prebidAnalyticsEventHandler({eventType, args}) { - logMessage(eventType, Object.assign({}, args)); - switch (eventType) { - case AUCTION_INIT: - onAuctionInit(args); - break; - case BID_REQUESTED: - onBidRequested(args); - break; - case BID_RESPONSE: - onBidResponse(args); - break; - case BID_TIMEOUT: - onBidTimeout(args); - break; - case AUCTION_END: - onAuctionEnd(args); - break; - case BID_WON: - onBidWon(args); - break; - case SLOT_LOADED: - onSlotLoadedV2(args); - break; - } -} - -/** - * @typedef {Object} PbAuction - * @property {string} auctionId - Auction ID of the request this bid responded to - * @property {number} timestamp //: 1586675964364 - * @property {number} auctionEnd - timestamp of when auction ended //: 1586675964364 - * @property {string} auctionStatus //: "inProgress" - * @property {Array} adUnits //: [{…}] - * @property {string} adUnitCodes //: ["video1"] - * @property {string} labels //: undefined - * @property {Array} bidderRequests //: (2) [{…}, {…}] - * @property {Array} noBids //: [] - * @property {Array} bidsReceived //: [] - * @property {Array} winningBids //: [] - * @property {number} timeout //: 3000 - * @property {Object} config //: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1}/* - */ - -function onAuctionInit({auctionId, timestamp: startTime, timeout, adUnitCodes}) { - auctionMap[auctionId] = { - id: auctionId, - startTime, - endTime: void (0), - timeout, - auctionOrder, - userIds: [], - adUnitCodesCount: adUnitCodes.length, - adunitCodesRenderedCount: 0, - state: AUCTION_STATES.INIT, - auctionSendDelayTimer: void (0), - }; - - // setup adunit properties in map - auctionMap[auctionId].adUnitCodeToAdUnitMap = adUnitCodes.reduce((obj, adunitCode) => { - obj[adunitCode] = { - code: adunitCode, - adPosition: void (0), - bidRequestsMap: {} - }; - return obj; - }, {}); - - auctionOrder++; -} - -/** - * @typedef {Object} PbBidRequest - * @property {string} auctionId - Auction ID of the request this bid responded to - * @property {number} auctionStart //: 1586675964364 - * @property {Object} refererInfo - * @property {PbBidderRequest} bids - * @property {number} start - Start timestamp of the bidder request - * - */ - -/** - * @typedef {Object} PbBidderRequest - * @property {string} adUnitCode - Name of div or google adunit path - * @property {string} bidder - Bame of bidder - * @property {string} bidId - Identifies the bid request - * @property {Object} mediaTypes - * @property {Object} params - * @property {string} src - * @property {Object} userId - Map of userId module to module object - */ - -/** - * Tracks the bid request - * @param {PbBidRequest} bidRequest - */ -function onBidRequested(bidRequest) { - const {auctionId, bids: bidderRequests, start, timeout} = bidRequest; - const auction = auctionMap[auctionId]; - const adUnitCodeToAdUnitMap = auction.adUnitCodeToAdUnitMap; - - bidderRequests.forEach(bidderRequest => { - const { adUnitCode, bidder, bidId: requestId, mediaTypes, params, src, userId } = bidderRequest; - - auction.userIds.push(userId); - adUnitCodeToAdUnitMap[adUnitCode].bidRequestsMap[requestId] = { - bidder, - params, - mediaTypes, - source: src, - startTime: start, - timedOut: false, - timeLimit: timeout, - bids: {} - }; - }); -} - -/** - * - * @param {BidResponse} bidResponse - */ -function onBidResponse(bidResponse) { - let { - auctionId, - adUnitCode, - requestId, - cpm, - creativeId, - requestTimestamp, - responseTimestamp, - ts, - mediaType, - dealId, - ttl, - netRevenue, - currency, - originalCpm, - originalCurrency, - width, - height, - timeToRespond: latency, - adId, - meta - } = bidResponse; - - auctionMap[auctionId].adUnitCodeToAdUnitMap[adUnitCode].bidRequestsMap[requestId].bids[adId] = { - cpm, - creativeId, - requestTimestamp, - responseTimestamp, - ts, - adId, - meta, - mediaType, - dealId, - ttl, - netRevenue, - currency, - originalCpm, - originalCurrency, - width, - height, - latency, - winner: false, - rendered: false, - renderTime: 0, - }; -} - -function onBidTimeout(args) { - _each(args, ({auctionId, adUnitCode, bidId: requestId}) => { - let timedOutRequest = deepAccess(auctionMap, - `${auctionId}.adUnitCodeToAdUnitMap.${adUnitCode}.bidRequestsMap.${requestId}`); - - if (timedOutRequest) { - timedOutRequest.timedOut = true; - } - }); -} -/** - * - * @param {PbAuction} endedAuction - */ -function onAuctionEnd(endedAuction) { - let auction = auctionMap[endedAuction.auctionId]; - - if (!auction) { - return; - } - - clearAuctionTimer(auction); - auction.endTime = endedAuction.auctionEnd; - auction.state = AUCTION_STATES.ENDED; - delayedSend(auction); -} - -/** - * - * @param {BidResponse} bidResponse - */ -function onBidWon(bidResponse) { - const { auctionId, adUnitCode, requestId, adId } = bidResponse; - let winningBid = deepAccess(auctionMap, - `${auctionId}.adUnitCodeToAdUnitMap.${adUnitCode}.bidRequestsMap.${requestId}.bids.${adId}`); - - if (winningBid) { - winningBid.winner = true; - const auction = auctionMap[auctionId]; - if (auction.sent) { - const endpoint = (analyticsConfig.endpoint || ENDPOINT) + 'event'; - const bidder = auction.adUnitCodeToAdUnitMap[adUnitCode].bidRequestsMap[requestId].bidder; - ajax(`${endpoint}?t=win&b=${adId}&a=${analyticsConfig.orgId}&bidder=${bidder}&ts=${auction.startTime}`, - () => { - logInfo(`Openx Analytics - Sending complete impression event for ${adId} at ${Date.now()}`) - }); - } else { - logInfo(`Openx Analytics - impression event for ${adId} will be sent with auction data`) - } - } -} - -/** - * - * @param {GoogleTagSlot} slot - * @param {string} serviceName - */ -function onSlotLoadedV2({ slot }) { - const renderTime = Date.now(); - const elementId = slot.getSlotElementId(); - const bidId = slot.getTargeting('hb_adid')[0]; - - let [auction, adUnit, bid] = getPathToBidResponseByBidId(bidId); - - if (!auction) { - // attempt to get auction by adUnitCode - auction = getAuctionByGoogleTagSLot(slot); - - if (!auction) { - return; // slot is not participating in an active prebid auction - } - } - - clearAuctionTimer(auction); - - // track that an adunit code has completed within an auction - auction.adunitCodesRenderedCount++; - - // mark adunit as rendered - if (bid) { - let {x, y} = getPageOffset(); - bid.rendered = true; - bid.renderTime = renderTime; - adUnit.adPosition = isAtf(elementId, x, y) ? 'ATF' : 'BTF'; - } - - if (auction.adunitCodesRenderedCount === auction.adUnitCodesCount) { - auction.state = AUCTION_STATES.COMPLETED; - } - - // prepare to send regardless if auction is complete or not as a failsafe in case not all events are tracked - // add additional padding when not all slots are rendered - delayedSend(auction); -} - -function isAtf(elementId, scrollLeft = 0, scrollTop = 0) { - let elem = document.querySelector('#' + elementId); - let isAtf = false; - if (elem) { - let bounding = elem.getBoundingClientRect(); - if (bounding) { - let windowWidth = (window.innerWidth || document.documentElement.clientWidth); - let windowHeight = (window.innerHeight || document.documentElement.clientHeight); - - // intersection coordinates - let left = Math.max(0, bounding.left + scrollLeft); - let right = Math.min(windowWidth, bounding.right + scrollLeft); - let top = Math.max(0, bounding.top + scrollTop); - let bottom = Math.min(windowHeight, bounding.bottom + scrollTop); - - let intersectionWidth = right - left; - let intersectionHeight = bottom - top; - - let intersectionArea = (intersectionHeight > 0 && intersectionWidth > 0) ? (intersectionHeight * intersectionWidth) : 0; - let adSlotArea = (bounding.right - bounding.left) * (bounding.bottom - bounding.top); - - if (adSlotArea > 0) { - // Atleast 50% of intersection in window - isAtf = intersectionArea * 2 >= adSlotArea; - } - } - } else { - logWarn('OX: DOM element not for id ' + elementId); - } - return isAtf; -} - -// backwards compatible pageOffset from https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX -function getPageOffset() { - var x = (window.pageXOffset !== undefined) - ? window.pageXOffset - : (document.documentElement || document.body.parentNode || document.body).scrollLeft; - - var y = (window.pageYOffset !== undefined) - ? window.pageYOffset - : (document.documentElement || document.body.parentNode || document.body).scrollTop; - return {x, y}; -} - -function delayedSend(auction) { - if (auction.sent) { - return; - } - const delayTime = auction.adunitCodesRenderedCount === auction.adUnitCodesCount - ? analyticsConfig.payloadWaitTime - : analyticsConfig.payloadWaitTime + analyticsConfig.payloadWaitTimePadding; - - auction.auctionSendDelayTimer = setTimeout(() => { - auction.sent = true; // any BidWon emitted after this will be recorded separately - let payload = JSON.stringify([buildAuctionPayload(auction)]); - - ajax(analyticsConfig.endpoint || ENDPOINT, () => { - logInfo(`OpenX Analytics - Sending complete auction at ${Date.now()}`); - }, payload, { contentType: 'application/json' }); - }, delayTime); -} - -function clearAuctionTimer(auction) { - // reset the delay timer to send the auction data - if (auction.auctionSendDelayTimer) { - clearTimeout(auction.auctionSendDelayTimer); - auction.auctionSendDelayTimer = void (0); - } -} - -/** - * Returns the path to a bid (auction, adunit, bidRequest, and bid) based on a bidId - * @param {string} bidId - * @returns {Array<*>} - */ -function getPathToBidResponseByBidId(bidId) { - let auction; - let adUnit; - let bidResponse; - - if (!bidId) { - return []; - } - - _each(auctionMap, currentAuction => { - // skip completed auctions - if (currentAuction.state === AUCTION_STATES.COMPLETED) { - return; - } - - _each(currentAuction.adUnitCodeToAdUnitMap, (currentAdunit) => { - _each(currentAdunit.bidRequestsMap, currentBiddRequest => { - _each(currentBiddRequest.bids, (currentBidResponse, bidResponseId) => { - if (bidId === bidResponseId) { - auction = currentAuction; - adUnit = currentAdunit; - bidResponse = currentBidResponse; - } - }); - }); - }); - }); - return [auction, adUnit, bidResponse]; -} - -function getAuctionByGoogleTagSLot(slot) { - let slotAdunitCodes = [slot.getSlotElementId(), slot.getAdUnitPath()]; - let slotAuction; - - _each(auctionMap, auction => { - if (auction.state === AUCTION_STATES.COMPLETED) { - return; - } - - _each(auction.adUnitCodeToAdUnitMap, (bidderRequestIdMap, adUnitCode) => { - if (includes(slotAdunitCodes, adUnitCode)) { - slotAuction = auction; - } - }); - }); - - return slotAuction; -} - -function buildAuctionPayload(auction) { - let {startTime, endTime, state, timeout, auctionOrder, userIds, adUnitCodeToAdUnitMap, id} = auction; - const auctionId = id; - let {orgId, publisherPlatformId, publisherAccountId, campaign, testCode, configId, optimizerConfig} = analyticsConfig; - - return { - auctionId, - adapterVersion: ADAPTER_VERSION, - schemaVersion: SCHEMA_VERSION, - orgId, - publisherPlatformId, - publisherAccountId, - configId, - optimizerConfig, - campaign, - state, - startTime, - endTime, - timeLimit: timeout, - auctionOrder, - deviceType: detectMob() ? 'Mobile' : 'Desktop', - deviceOSType: detectOS(), - browser: detectBrowser(), - testCode: testCode, - // return an array of module name that have user data - userIdProviders: buildUserIdProviders(userIds), - adUnits: buildAdUnitsPayload(adUnitCodeToAdUnitMap), - }; - - function buildAdUnitsPayload(adUnitCodeToAdUnitMap) { - return _map(adUnitCodeToAdUnitMap, (adUnit) => { - let {code, adPosition} = adUnit; - - return { - code, - adPosition, - bidRequests: buildBidRequestPayload(adUnit.bidRequestsMap) - }; - - function buildBidRequestPayload(bidRequestsMap) { - return _map(bidRequestsMap, (bidRequest) => { - let {bidder, source, bids, mediaTypes, timeLimit, timedOut} = bidRequest; - return { - bidder, - source, - hasBidderResponded: Object.keys(bids).length > 0, - availableAdSizes: getMediaTypeSizes(mediaTypes), - availableMediaTypes: getMediaTypes(mediaTypes), - timeLimit, - timedOut, - bidResponses: _map(bidRequest.bids, (bidderBidResponse) => { - let { - adId, - cpm, - creativeId, - ts, - meta, - mediaType, - dealId, - ttl, - netRevenue, - currency, - width, - height, - latency, - winner, - rendered, - renderTime - } = bidderBidResponse; - - return { - bidId: adId, - microCpm: cpm * 1000000, - netRevenue, - currency, - mediaType, - height, - width, - size: `${width}x${height}`, - dealId, - latency, - ttl, - winner, - creativeId, - ts, - rendered, - renderTime, - meta - } - }) - } - }); - } - }); - } - - function buildUserIdProviders(userIds) { - return _map(userIds, (userId) => { - return _map(userId, (id, module) => { - return hasUserData(module, id) ? module : false - }).filter(module => module); - }).reduce(flatten, []).filter(uniques).sort(); - } - - function hasUserData(module, idOrIdObject) { - let normalizedId; - - switch (module) { - case 'digitrustid': - normalizedId = deepAccess(idOrIdObject, 'data.id'); - break; - case 'lipb': - normalizedId = idOrIdObject.lipbid; - break; - default: - normalizedId = idOrIdObject; - } - - return !isEmpty(normalizedId); - } - - function getMediaTypeSizes(mediaTypes) { - return _map(mediaTypes, (mediaTypeConfig, mediaType) => { - return parseSizesInput(mediaTypeConfig.sizes) - .map(size => `${mediaType}_${size}`); - }).reduce(flatten, []); - } - - function getMediaTypes(mediaTypes) { - return _map(mediaTypes, (mediaTypeConfig, mediaType) => mediaType); - } } diff --git a/modules/openxAnalyticsAdapter.md b/modules/openxAnalyticsAdapter.md index af40486f2a4..357e1520eb4 100644 --- a/modules/openxAnalyticsAdapter.md +++ b/modules/openxAnalyticsAdapter.md @@ -5,6 +5,10 @@ --- +# NOTE: OPENX NO LONGER OFFERS ANALYTICS +# THIS ADAPTER NO LONGER FUNCTIONS AND +# WILL BE REMOVED IN PREBID 8 + # About this Guide This implementation guide walks through the flow of onboarding an alpha Publisher to test OpenX’s new Analytics Adapter. diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 85dcfbb3b47..14525cd0cfc 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -28,7 +28,7 @@ export const USER_ID_CODE_TO_QUERY_ARG = { britepoolid: 'britepoolid', // BritePool ID criteoId: 'criteoid', // CriteoID fabrickId: 'nuestarid', // Fabrick ID by Nuestar - haloId: 'audigentid', // Halo ID from Audigent + hadronId: 'audigentid', // Hadron ID from Audigent id5id: 'id5id', // ID5 ID idl_env: 'lre', // LiveRamp IdentityLink IDP: 'zeotapid', // zeotapIdPlus ID+ @@ -44,7 +44,6 @@ export const USER_ID_CODE_TO_QUERY_ARG = { tapadId: 'tapadid', // Tapad Id tdid: 'ttduuid', // The Trade Desk Unified ID uid2: 'uid2', // Unified ID 2.0 - flocId: 'floc', // Chrome FLoC, admixerId: 'admixerid', // AdMixer ID deepintentId: 'deepintentid', // DeepIntent ID dmdId: 'dmdid', // DMD Marketing Corp ID @@ -214,13 +213,11 @@ function getViewportDimensions(isIfr) { } catch (e) { return; } - docEl = tDoc.documentElement; body = tDoc.body; width = tWin.innerWidth || docEl.clientWidth || body.clientWidth; height = tWin.innerHeight || docEl.clientHeight || body.clientHeight; } else { - docEl = tDoc.documentElement; width = tWin.innerWidth || docEl.clientWidth; height = tWin.innerHeight || docEl.clientHeight; } @@ -259,7 +256,7 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { let defaultParams; defaultParams = { - ju: config.getConfig('pageUrl') || bidderRequest.refererInfo.referer, + ju: bidderRequest.refererInfo.page, ch: document.charSet || document.characterSet, res: `${screen.width}x${screen.height}x${screen.colorDepth}`, ifr: isInIframe, @@ -271,12 +268,12 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { nocache: new Date().getTime() }; - const userDataSegments = buildFpdQueryParams('ortb2.user.data'); + const userDataSegments = buildFpdQueryParams('user.data', bidderRequest.ortb2); if (userDataSegments.length > 0) { defaultParams.sm = userDataSegments; } - const siteContentDataSegments = buildFpdQueryParams('ortb2.site.content.data'); + const siteContentDataSegments = buildFpdQueryParams('site.content.data', bidderRequest.ortb2); if (siteContentDataSegments.length > 0) { defaultParams.scsm = siteContentDataSegments; } @@ -319,8 +316,8 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { return defaultParams; } -function buildFpdQueryParams(fpdPath) { - const firstPartyData = config.getConfig(fpdPath); +function buildFpdQueryParams(fpdPath, ortb2) { + const firstPartyData = deepAccess(ortb2, fpdPath); if (!Array.isArray(firstPartyData) || !firstPartyData.length) { return ''; } @@ -350,16 +347,13 @@ function appendUserIdsToQueryParams(queryParams, userIds) { case 'merkleId': queryParams[key] = userIdObjectOrValue.id; break; - case 'flocId': - queryParams[key] = userIdObjectOrValue.id; - break; case 'uid2': queryParams[key] = userIdObjectOrValue.id; break; case 'lipb': queryParams[key] = userIdObjectOrValue.lipbid; if (Array.isArray(userIdObjectOrValue.segments) && userIdObjectOrValue.segments.length > 0) { - const liveIntentSegments = 'liveintent:' + userIdObjectOrValue.segments.join('|') + const liveIntentSegments = 'liveintent:' + userIdObjectOrValue.segments.join('|'); queryParams.sm = `${queryParams.sm ? queryParams.sm + ',' : ''}${liveIntentSegments}`; } break; @@ -508,7 +502,7 @@ function generateVideoParameters(bid, bidderRequest) { video: openRtbParams } ] - } + }; queryParams['openrtb'] = JSON.stringify(openRtbReq); @@ -531,7 +525,7 @@ function generateVideoParameters(bid, bidderRequest) { let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { - queryParams.aucs = encodeURIComponent(gpid) + queryParams.aucs = encodeURIComponent(gpid); } // each video bid makes a separate request diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index 68e41a93b18..28dba424fb2 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -8,7 +8,9 @@ Maintainer: team-openx@openx.com # Description -Module that connects to OpenX's demand sources +Module that connects to OpenX's demand sources. +Note there is an updated version of the OpenX bid adapter called openxOrtbBidAdapter. +Publishers are welcome to test the other adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. # Bid Parameters ## Banner diff --git a/modules/openxOrtbBidAdapter.js b/modules/openxOrtbBidAdapter.js new file mode 100644 index 00000000000..dc79ffb47b2 --- /dev/null +++ b/modules/openxOrtbBidAdapter.js @@ -0,0 +1,379 @@ +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {includes} from '../src/polyfill.js'; + +const bidderConfig = 'hb_pb_ortb'; +const bidderVersion = '1.0'; +const VIDEO_TARGETING = ['startdelay', 'mimes', 'minduration', 'maxduration', 'delivery', + 'startdelay', 'skip', 'playbackmethod', 'api', 'protocol', 'boxingallowed', 'maxextended', + 'linearity', 'delivery', 'protocols', 'placement', 'minbitrate', 'maxbitrate', 'battr', 'ext']; +export const REQUEST_URL = 'https://rtb.openx.net/openrtbb/prebidjs'; +export const SYNC_URL = 'https://u.openx.net/w/1.0/pd'; +export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; +export const spec = { + code: 'openx', + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + transformBidParams +}; + +registerBidder(spec); + +function transformBidParams(params, isOpenRtb) { + return utils.convertTypes({ + 'unit': 'string', + 'customFloor': 'number' + }, params); +} + +function isBidRequestValid(bidRequest) { + const hasDelDomainOrPlatform = bidRequest.params.delDomain || + bidRequest.params.platform; + + if (utils.deepAccess(bidRequest, 'mediaTypes.banner') && + hasDelDomainOrPlatform) { + return !!bidRequest.params.unit || + utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0; + } + + return !!(bidRequest.params.unit && hasDelDomainOrPlatform); +} + +function buildRequests(bids, bidderRequest) { + let videoBids = bids.filter(bid => isVideoBid(bid)); + let bannerBids = bids.filter(bid => isBannerBid(bid)); + let requests = bannerBids.length ? [createBannerRequest(bannerBids, bidderRequest)] : []; + videoBids.forEach(bid => { + requests.push(createVideoRequest(bid, bidderRequest)); + }); + return requests; +} + +function createBannerRequest(bids, bidderRequest) { + let data = getBaseRequest(bids[0], bidderRequest); + data.imp = bids.map(bid => { + const floor = getFloor(bid, BANNER); + let imp = { + id: bid.bidId, + tagid: bid.params.unit, + banner: { + format: toFormat(bid.mediaTypes.banner.sizes), + topframe: utils.inIframe() ? 0 : 1 + }, + ext: {divid: bid.adUnitCode} + }; + + if (bidderRequest.fledgeEnabled) { + imp.ext.ae = bid?.ortb2Imp?.ext?.ae + } + + enrichImp(imp, bid, floor); + return imp; + }); + return { + method: 'POST', + url: config.getConfig('openxOrtbUrl') || REQUEST_URL, + data: data + } +} + +function toFormat(sizes) { + return sizes.map((s) => { + return { w: s[0], h: s[1] }; + }); +} + +function enrichImp(imp, bid, floor) { + if (bid.params.customParams) { + utils.deepSetValue(imp, 'ext.customParams', bid.params.customParams); + } + if (floor > 0) { + imp.bidfloor = floor; + imp.bidfloorcur = 'USD'; + } else if (bid.params.customFloor) { + imp.bidfloor = bid.params.customFloor; + } + if (bid.ortb2Imp && bid.ortb2Imp.ext && bid.ortb2Imp.ext.data) { + imp.ext.data = bid.ortb2Imp.ext.data; + } +} + +function createVideoRequest(bid, bidderRequest) { + let width; + let height; + const videoMediaType = utils.deepAccess(bid, `mediaTypes.video`); + const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize'); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + const floor = getFloor(bid, VIDEO); + + // normalize config for video size + if (utils.isArray(bid.sizes) && bid.sizes.length === 2 && !utils.isArray(bid.sizes[0])) { + width = parseInt(bid.sizes[0], 10); + height = parseInt(bid.sizes[1], 10); + } else if (utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0]) && bid.sizes[0].length === 2) { + width = parseInt(bid.sizes[0][0], 10); + height = parseInt(bid.sizes[0][1], 10); + } else if (utils.isArray(playerSize) && playerSize.length === 2) { + width = parseInt(playerSize[0], 10); + height = parseInt(playerSize[1], 10); + } + + let data = getBaseRequest(bid, bidderRequest); + data.imp = [{ + id: bid.bidId, + tagid: bid.params.unit, + video: { + w: width, + h: height, + topframe: utils.inIframe() ? 0 : 1 + }, + ext: {divid: bid.adUnitCode} + }]; + + enrichImp(data.imp[0], bid, floor); + + if (context) { + if (context === 'instream') { + data.imp[0].video.placement = 1; + } else if (context === 'outstream') { + data.imp[0].video.placement = 4; + } + } + + // backward compatability for video params + let videoParams = bid.params.video || bid.params.openrtb || {}; + if (utils.isArray(videoParams.imp)) { + videoParams = videoParams[0].video; + } + + Object.keys(videoParams) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => data.imp[0].video[param] = videoParams[param]); + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => data.imp[0].video[param] = videoMediaType[param]); + + return { + method: 'POST', + url: REQUEST_URL, + data: data + }; +} + +function getBaseRequest(bid, bidderRequest) { + let req = { + id: bidderRequest.auctionId, + cur: [config.getConfig('currency.adServerCurrency') || 'USD'], + at: 1, + tmax: config.getConfig('bidderTimeout'), + site: { + page: config.getConfig('pageUrl') || bidderRequest.refererInfo.referer + }, + regs: { + coppa: (config.getConfig('coppa') === true || bid.params.coppa) ? 1 : 0, + }, + device: { + dnt: (utils.getDNT() || bid.params.doNotTrack) ? 1 : 0, + h: screen.height, + w: screen.width, + ua: window.navigator.userAgent, + language: window.navigator.language.split('-').shift() + }, + ext: { + bc: `${bidderConfig}_${bidderVersion}` + } + }; + + if (bid.params.platform) { + utils.deepSetValue(req, 'ext.platform', bid.params.platform); + } + if (bid.params.delDomain) { + utils.deepSetValue(req, 'ext.delDomain', bid.params.delDomain); + } + if (bid.params.response_template_name) { + utils.deepSetValue(req, 'ext.response_template_name', bid.params.response_template_name); + } + if (bid.params.test) { + req.test = 1; + } + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + utils.deepSetValue(req, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0); + } + if (bidderRequest.gdprConsent.consentString !== undefined) { + utils.deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + if (bidderRequest.gdprConsent.addtlConsent !== undefined) { + utils.deepSetValue(req, 'user.ext.ConsentedProvidersSettings.consented_providers', bidderRequest.gdprConsent.addtlConsent); + } + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + if (bid.schain) { + utils.deepSetValue(req, 'source.ext.schain', bid.schain); + } + if (bid.userIdAsEids) { + utils.deepSetValue(req, 'user.ext.eids', bid.userIdAsEids); + } + const commonFpd = bidderRequest.ortb2 || {}; + if (commonFpd.site) { + utils.mergeDeep(req, {site: commonFpd.site}); + } + if (commonFpd.user) { + utils.mergeDeep(req, {user: commonFpd.user}); + } + return req; +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function getFloor(bid, mediaType) { + let floor = 0; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && + !isNaN(parseFloat(floorInfo.floor))) { + floor = Math.max(floor, parseFloat(floorInfo.floor)); + } + } + + return floor; +} + +function interpretOrtbResponse(resp, req) { + if (!resp.body) { + resp.body = {nbr: 0}; + } + + // pass these from request to the responses for use in userSync + if (req.data.ext) { + if (req.data.ext.delDomain) { + utils.deepSetValue(resp, 'body.ext.delDomain', req.data.ext.delDomain); + } + if (req.data.ext.platform) { + utils.deepSetValue(resp, 'body.ext.platform', req.data.ext.platform); + } + } + + const respBody = resp.body; + if (!respBody || 'nbr' in respBody) { + return []; + } + + let bids = []; + respBody.seatbid.forEach(seatbid => { + bids = [...bids, ...seatbid.bid.map(bid => { + let response = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + dealId: bid.dealid, + currency: respBody.cur || 'USD', + netRevenue: true, + ttl: 300, + mediaType: 'banner' in req.data.imp[0] ? BANNER : VIDEO, + meta: { advertiserDomains: bid.adomain } + }; + + if (response.mediaType === VIDEO) { + if (bid.nurl) { + response.vastUrl = bid.nurl; + } else { + response.vastXml = bid.adm; + } + } else { + response.ad = bid.adm; + } + + if (bid.ext) { + response.meta.networkId = bid.ext.dsp_id; + response.meta.advertiserId = bid.ext.buyer_id; + response.meta.brandId = bid.ext.brand_id; + } + + if (respBody.ext && respBody.ext.paf) { + response.meta.paf = Object.assign({}, respBody.ext.paf); + response.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); + } + + return response; + })]; + }); + + return bids; +} + +function interpretResponse(resp, req) { + const bids = interpretOrtbResponse(resp, req); + let fledgeAuctionConfigs = utils.deepAccess(resp, 'body.ext.fledge_auction_configs'); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { + return Object.assign({ + bidId, + auctionSignals: {} + }, cfg); + }); + return { + bids, + fledgeAuctionConfigs, + } + } + return bids; +} + +/** + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let queryParamStrings = []; + let syncUrl = SYNC_URL; + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + if (responses.length > 0 && responses[0].body && responses[0].body.ext) { + const ext = responses[0].body.ext; + if (ext.delDomain) { + syncUrl = `https://${ext.delDomain}/w/1.0/pd` + } else if (ext.platform) { + queryParamStrings.push('ph=' + ext.platform) + } + } else { + queryParamStrings.push('ph=' + DEFAULT_PH) + } + return [{ + type: pixelType, + url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` + }]; + } +} diff --git a/modules/openxOrtbBidAdapter.md b/modules/openxOrtbBidAdapter.md new file mode 100644 index 00000000000..fd926b27b9f --- /dev/null +++ b/modules/openxOrtbBidAdapter.md @@ -0,0 +1,105 @@ +# Overview + +``` +Module Name: OpenX OpenRTB Bidder Adapter +Module Type: Bidder Adapter +Maintainer: team-openx@openx.com +``` + +# Description + +This is an updated version of the OpenX bid adapter which calls our new serving architecture. +Publishers are welcome to test this adapter and give feedback. Please note you should only include either openxBidAdapter or openxOrtbBidAdapter in your build. + +# Bid Parameters +## Banner + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `delDomain` or `platform` | required | String | OpenX delivery domain or platform id provided by your OpenX representative. | "PUBLISHER-d.openx.net" or "555not5a-real-plat-form-id0123456789" +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` +| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue | 1.50 +| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. | true + +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" +| `video` | optional | OpenRTB video subtypes | Alternatively can be added under adUnit.mediaTypes.video | `{ video: {mimes: ['video/mp4']}` + + +# Example +```javascript +var adUnits = [ + { + code: 'test-div', + sizes: [[728, 90]], // a display size + mediaTypes: {'banner': {}}, + bids: [ + { + bidder: 'openx', + params: { + unit: '539439964', + delDomain: 'se-demo-d.openx.net', + customParams: { + key1: 'v1', + key2: ['v2', 'v3'] + }, + } + }, { + bidder: 'openx', + params: { + unit: '539439964', + platform: 'a3aece0c-9e80-4316-8deb-faf804779bd1', + customParams: { + key1: 'v1', + key2: ['v2', 'v3'] + }, + } + } + ] + }, + { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'openx', + params: { + unit: '1611023124', + delDomain: 'PUBLISHER-d.openx.net', + video: { + mimes: ['video/x-ms-wmv, video/mp4'] + } + } + }] + } +]; +``` + +# Configuration +Add the following code to enable user syncing. By default, Prebid.js version 0.34.0+ turns off user syncing through iframes. +OpenX strongly recommends enabling user syncing through iframes. This functionality improves DSP user match rates and increases the +OpenX bid rate and bid price. Be sure to call `pbjs.setConfig()` only once. + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true + } +}); +``` + +# Additional Details +[Banner Ads](https://docs.openx.com/publishers/prebid-adapter-web/) + +[Video Ads](https://docs.openx.com/publishers/prebid-adapter-video/) + diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index 61ea8cdcb76..aa548debf32 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -1,9 +1,22 @@ -import { logWarn, isArray, isStr, triggerPixel, deepAccess, deepSetValue, isPlainObject, generateUUID, parseUrl, isFn, getDNT, logError } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; -import { Renderer } from '../src/Renderer.js'; -import { OUTSTREAM } from '../src/video.js'; +import { + deepAccess, + deepSetValue, + generateUUID, + getDNT, + isArray, + isFn, + isPlainObject, + isStr, + logError, + logWarn, + triggerPixel +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {Renderer} from '../src/Renderer.js'; +import {OUTSTREAM} from '../src/video.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'operaads'; @@ -106,6 +119,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + return validBidRequests.map(validBidRequest => (buildOpenRtbBidRequest(validBidRequest, bidderRequest))) }, @@ -209,8 +225,6 @@ export const spec = { * @returns {Request} */ function buildOpenRtbBidRequest(bidRequest, bidderRequest) { - const pageReferrer = deepAccess(bidderRequest, 'refererInfo.referer'); - // build OpenRTB request body const payload = { id: bidderRequest.auctionId, @@ -220,9 +234,10 @@ function buildOpenRtbBidRequest(bidRequest, bidderRequest) { device: getDevice(), site: { id: String(deepAccess(bidRequest, 'params.publisherId')), - domain: getDomain(pageReferrer), - page: pageReferrer, - ref: window.self === window.top ? document.referrer : '', + // TODO: does the fallback make sense here? + domain: bidderRequest?.refererInfo?.domain || window.location.host, + page: bidderRequest?.refererInfo?.page, + ref: bidderRequest?.refererInfo?.ref || '', }, at: 1, bcat: getBcat(bidRequest), @@ -534,7 +549,7 @@ function createImp(bidRequest) { const floorDetail = getBidFloor(bidRequest, { mediaType: mediaType || '*', size: size || '*' - }) + }); impItem.bidfloor = floorDetail.floor; impItem.bidfloorcur = floorDetail.currency; @@ -680,23 +695,6 @@ function getUserId(bidRequest) { return generateUUID(); } -/** - * Get publisher domain - * - * @param {String} referer - * @returns {String} domain - */ -function getDomain(referer) { - let domain; - - if (!(domain = config.getConfig('publisherDomain'))) { - const u = parseUrl(referer); - domain = u.hostname; - } - - return domain.replace(/^https?:\/\/([\w\-\.]+)(?::\d+)?/, '$1'); -} - /** * Get bid floor price * diff --git a/modules/optimaticBidAdapter.md b/modules/optimaticBidAdapter.md deleted file mode 100644 index edaa3da90f6..00000000000 --- a/modules/optimaticBidAdapter.md +++ /dev/null @@ -1,30 +0,0 @@ -# Overview - -``` -Module Name: Optimatic Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid@optimatic.com -``` - -# Description - -Optimatic Bid Adapter Module connects to Optimatic Demand Sources for Video Ads - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - sizes: [[640,480]], // a video size - bids: [ - { - bidder: "optimatic", - params: { - placement: "2chy7Gc2eSQL", - bidfloor: 2.5 - } - } - ] - }, - ]; -``` diff --git a/modules/optimonAnalyticsAdapter.js b/modules/optimonAnalyticsAdapter.js index 34b2778afc9..82bc18f605d 100644 --- a/modules/optimonAnalyticsAdapter.js +++ b/modules/optimonAnalyticsAdapter.js @@ -8,7 +8,7 @@ * */ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; const optimonAnalyticsAdapter = adapter({ diff --git a/modules/optoutBidAdapter.js b/modules/optoutBidAdapter.js index d218a65bf90..f7b5934665c 100644 --- a/modules/optoutBidAdapter.js +++ b/modules/optoutBidAdapter.js @@ -1,6 +1,7 @@ import { deepAccess } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const BIDDER_CODE = 'optout'; @@ -19,16 +20,6 @@ function getCurrency() { return cur; } -function hasPurpose1Consent(bidderRequest) { - let result = false; - if (bidderRequest && bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.apiVersion === 2) { - result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); - } - } - return result; -} - export const spec = { code: BIDDER_CODE, @@ -36,7 +27,7 @@ export const spec = { return !!bid.params.publisher && !!bid.params.adslot; }, - buildRequests: function(validBidRequests) { + buildRequests: function(validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { let endPoint = 'https://adscience-nocookie.nl/prebid/display'; let consentString = ''; @@ -44,7 +35,7 @@ export const spec = { if (bidRequest.gdprConsent) { gdpr = (typeof bidRequest.gdprConsent.gdprApplies === 'boolean') ? Number(bidRequest.gdprConsent.gdprApplies) : 0; consentString = bidRequest.gdprConsent.consentString; - if (!gdpr || hasPurpose1Consent(bidRequest)) { + if (!gdpr || hasPurpose1Consent(bidRequest.gdprConsent)) { endPoint = 'https://prebid.adscience.nl/prebid/display'; } } @@ -57,7 +48,7 @@ export const spec = { adSlot: bidRequest.params.adslot, cur: getCurrency(), url: getDomain(bidRequest), - ortb2: config.getConfig('ortb2'), + ortb2: bidderRequest.ortb2, consent: consentString, gdpr: gdpr @@ -73,7 +64,7 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent) { if (gdprConsent) { let gdpr = (typeof gdprConsent.gdprApplies === 'boolean') ? Number(gdprConsent.gdprApplies) : 0; - if (syncOptions.iframeEnabled && (!gdprConsent.gdprApplies || hasPurpose1Consent({gdprConsent}))) { + if (syncOptions.iframeEnabled && (!gdprConsent.gdprApplies || hasPurpose1Consent(gdprConsent))) { return [{ type: 'iframe', url: 'https://umframe.adscience.nl/matching/iframe?gdpr=' + gdpr + '&gdpr_consent=' + gdprConsent.consentString diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 38af3a8d1d6..9979b1fdc3b 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -2,6 +2,7 @@ import { isFn, isPlainObject } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const storageManager = getStorageManager({bidderCode: 'orbidder'}); @@ -78,11 +79,14 @@ export const spec = { * @return The requests for the orbidder /bid endpoint, i.e. the server. */ buildRequests(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const hostname = this.getHostname(); return validBidRequests.map((bidRequest) => { let referer = ''; if (bidderRequest && bidderRequest.refererInfo) { - referer = bidderRequest.refererInfo.referer || ''; + referer = bidderRequest.refererInfo.page || ''; } bidRequest.params.bidfloor = getBidFloor(bidRequest); diff --git a/modules/orbitsoftBidAdapter.md b/modules/orbitsoftBidAdapter.md deleted file mode 100644 index a18f075b6b1..00000000000 --- a/modules/orbitsoftBidAdapter.md +++ /dev/null @@ -1,60 +0,0 @@ -# Overview - -``` -Module Name: Orbitsoft Bidder Adapter -Module Type: Bidder Adapter -Maintainer: support@orbitsoft.com -``` - -# Description - -Module that connects to Orbitsoft's demand sources. The “sizes” option is not supported, and the size of the ad depends on the placement settings. You can use an optional “style” parameter to set the appearance only for text ad. Specify the “requestUrl” param to your Orbitsoft ad server header bidding endpoint. - -# Test Parameters -``` - var adUnits = [ - { - code: 'orbitsoft-div', - bids: [ - { - bidder: "orbitsoft", - params: { - placementId: '132', - requestUrl: 'https://orbitsoft.com/php/ads/hb.php', - style: { - title: { - family: 'Tahoma', - size: 'medium', - weight: 'normal', - style: 'normal', - color: '0053F9' - }, - description: { - family: 'Tahoma', - size: 'medium', - weight: 'normal', - style: 'normal', - color: '0053F9' - }, - url: { - family: 'Tahoma', - size: 'medium', - weight: 'normal', - style: 'normal', - color: '0053F9' - }, - colors: { - background: 'ffffff', - border: 'E0E0E0', - link: '5B99FE' - } - }, - customParams: { - macro_name: "macro_value" - } - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/otmBidAdapter.js b/modules/otmBidAdapter.js index e81bdfa9e6a..1230694fc65 100644 --- a/modules/otmBidAdapter.js +++ b/modules/otmBidAdapter.js @@ -45,13 +45,11 @@ export const spec = { const bidRequests = []; const tz = new Date().getTimezoneOffset() - const referrer = bidderRequest && bidderRequest.refererInfo ? bidderRequest.refererInfo.referer : ''; + // TODO: are these the right referer values? + const referrer = bidderRequest?.refererInfo?.page || ''; + const topOrigin = bidderRequest?.refererInfo?.domain || ''; _each(validBidRequests, (bid) => { - let topOrigin = '' - try { - if (isStr(referrer)) topOrigin = new URL(referrer).host - } catch (e) { /* do nothing */ } const domain = isStr(bid.params.domain) ? bid.params.domain : topOrigin const cur = getValue(bid.params, 'currency') || DEFAULT_CURRENCY const bidid = getBidIdParameter('bidId', bid) diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index e903f053c7e..3db1da0d689 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -8,6 +8,7 @@ import { NATIVE, BANNER } from '../src/mediaTypes.js'; import { deepAccess, deepSetValue, replaceAuctionPrice, _map, isArray } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'outbrain'; const GVLID = 164; @@ -53,13 +54,17 @@ export const spec = { ); }, buildRequests: (validBidRequests, bidderRequest) => { - const page = bidderRequest.refererInfo.referer; + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const ortb2 = bidderRequest.ortb2 || {}; + const page = bidderRequest.refererInfo.page; const ua = navigator.userAgent; const test = setOnAny(validBidRequests, 'params.test'); const publisher = setOnAny(validBidRequests, 'params.publisher'); - const bcat = setOnAny(validBidRequests, 'params.bcat'); - const badv = setOnAny(validBidRequests, 'params.badv'); - const eids = setOnAny(validBidRequests, 'userIdAsEids') + const bcat = ortb2.bcat || setOnAny(validBidRequests, 'params.bcat'); + const badv = ortb2.badv || setOnAny(validBidRequests, 'params.badv'); + const eids = setOnAny(validBidRequests, 'userIdAsEids'); + const wlang = ortb2.wlang; const cur = CURRENCY; const endpointUrl = config.getConfig('outbrain.bidderUrl'); const timeout = bidderRequest.timeout; @@ -106,6 +111,7 @@ export const spec = { imp: imps, bcat: bcat, badv: badv, + wlang: wlang, ext: { prebid: { channel: { diff --git a/modules/outconAdapter.md b/modules/outconAdapter.md deleted file mode 100644 index 88ed45396bc..00000000000 --- a/modules/outconAdapter.md +++ /dev/null @@ -1,26 +0,0 @@ -# Overview - -``` -Module Name: outconAdapter -Module Type: Bidder Adapter -Maintainer: mfolmer@dokkogroup.com.ar -``` - -# Description - -Module that connects to Outcon demand sources - -# Test Parameters -``` - var adUnits = [ - { - bidder: 'outcon', - params: { - internalId: '12345678', - publisher: '5d5d66f2306ea4114a37c7c2', - bidId: '123456789', - env: 'test' - } - } - ]; -``` \ No newline at end of file diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 3b5147907eb..3e59c2c8def 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -1,22 +1,30 @@ -import { logInfo, logError, deepAccess, logWarn, deepSetValue, isArray, contains, isStr, mergeDeep } from '../src/utils.js'; +import { + logInfo, + logError, + deepAccess, + logWarn, + deepSetValue, + isArray, + contains, + mergeDeep, + parseUrl +} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {getPriceBucketString} from '../src/cpmBucketManager.js'; import { Renderer } from '../src/Renderer.js'; - +import {getRefererInfo} from '../src/refererDetection.js'; const BIDDER_CODE = 'ozone'; - -const ORIGIN = 'https://elb.the-ozone-project.com' // applies only to auction & cookie +const ORIGIN = 'https://elb.the-ozone-project.com'; // applies only to auction & cookie const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; const ORIGIN_DEV = 'https://test.ozpr.net'; - -const OZONEVERSION = '2.7.0'; +const OZONEVERSION = '2.8.0'; export const spec = { gvlid: 524, - aliases: [{code: 'lmc', gvlid: 524}, {code: 'newspassid', gvlid: 524}], + aliases: [{code: 'lmc', gvlid: 524}], version: OZONEVERSION, code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER], @@ -30,10 +38,6 @@ export const spec = { 'cookieSyncUrl': ORIGIN + OZONECOOKIESYNC, 'rendererUrl': OZONE_RENDERER_URL }, - /** - * make sure that the whitelabel/default values are available in the propertyBag - * @param bid Object : the bid - */ loadWhitelabelData(bid) { if (this.propertyBag.whitelabel) { return; } this.propertyBag.whitelabel = JSON.parse(JSON.stringify(this.whitelabel_defaults)); @@ -52,7 +56,6 @@ export const spec = { this.propertyBag.whitelabel.auctionUrl = bidderConfig.endpointOverride.origin + AUCTIONURI; this.propertyBag.whitelabel.cookieSyncUrl = bidderConfig.endpointOverride.origin + OZONECOOKIESYNC; } - if (arr.hasOwnProperty('renderer')) { if (arr.renderer.match('%3A%2F%2F')) { this.propertyBag.whitelabel.rendererUrl = decodeURIComponent(arr['renderer']); @@ -91,16 +94,11 @@ export const spec = { getRendererUrl() { return this.propertyBag.whitelabel.rendererUrl; }, - /** - * Basic check to see whether required parameters are in the request. - * @param bid - * @returns {boolean} - */ isBidRequestValid(bid) { this.loadWhitelabelData(bid); logInfo('isBidRequestValid : ', config.getConfig(), bid); let adUnitCode = bid.adUnitCode; // adunit[n].code - let err1 = 'VALIDATION FAILED : missing {param} : siteId, placementId and publisherId are REQUIRED' + let err1 = 'VALIDATION FAILED : missing {param} : siteId, placementId and publisherId are REQUIRED'; if (!(bid.params.hasOwnProperty('placementId'))) { logError(err1.replace('{param}', 'placementId'), adUnitCode); return false; @@ -114,7 +112,7 @@ export const spec = { return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { - logError('VALIDATION FAILED : publisherId must be exactly 12 alphanumieric characters including hyphens', adUnitCode); + logError('VALIDATION FAILED : publisherId must be exactly 12 alphanumeric characters including hyphens', adUnitCode); return false; } if (!(bid.params.hasOwnProperty('siteId'))) { @@ -159,15 +157,9 @@ export const spec = { } return true; }, - - /** - * Split this out so that we can validate the placementId and also the override GET parameter ozstoredrequest - * @param placementId - */ isValidPlacementId(placementId) { return placementId.toString().match(/^[0-9]{10}$/); }, - buildRequests(validBidRequests, bidderRequest) { this.loadWhitelabelData(validBidRequests[0]); this.propertyBag.buildRequestsStart = new Date().getTime(); @@ -189,14 +181,13 @@ export const spec = { singleRequest = singleRequest !== false; // undefined & true will be true logInfo(`config ${whitelabelBidder}.singleRequest : `, singleRequest); let ozoneRequest = {}; // we only want to set specific properties on this, not validBidRequests[0].params - delete ozoneRequest.test; // don't allow test to be set in the config - ONLY use $_GET['pbjs_debug'] - - let fpd = config.getConfig('ortb2'); + logInfo('going to get ortb2 from bidder request...'); + let fpd = deepAccess(bidderRequest, 'ortb2', null); + logInfo('got fpd: ', fpd); if (fpd && deepAccess(fpd, 'user')) { logInfo('added FPD user object'); ozoneRequest.user = fpd.user; } - const getParams = this.getGetParametersAsObject(); const wlOztestmodeKey = whitelabelPrefix + 'testmode'; const isTestMode = getParams[wlOztestmodeKey] || null; // this can be any string, it's used for testing ads @@ -208,7 +199,8 @@ export const spec = { let placementId = placementIdOverrideFromGetParam || this.getPlacementId(ozoneBidRequest); // prefer to use a valid override param, else the bidRequest placement Id obj.id = ozoneBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder ozone made bid for unknown request ID: mb7953.859498327448. Ignoring." obj.tagid = placementId; - obj.secure = window.location.protocol === 'https:' ? 1 : 0; + let parsed = parseUrl(getRefererInfo().page); + obj.secure = parsed.protocol === 'https' ? 1 : 0; let arrBannerSizes = []; if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { @@ -288,7 +280,7 @@ export const spec = { } } if (fpd && deepAccess(fpd, 'site')) { - logInfo('added fpd.site'); + logInfo('adding fpd.site'); if (deepAccess(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', false)) { obj.ext[whitelabelBidder].customData[0].targeting = Object.assign(obj.ext[whitelabelBidder].customData[0].targeting, fpd.site); } else { @@ -300,7 +292,6 @@ export const spec = { } return obj; }); - let extObj = {}; extObj[whitelabelBidder] = {}; extObj[whitelabelBidder][whitelabelPrefix + '_pb_v'] = OZONEVERSION; @@ -311,8 +302,7 @@ export const spec = { extObj[whitelabelBidder].pubcid = userIds.pubcid; } } - - extObj[whitelabelBidder].pv = this.getPageId(); // attach the page ID that will be common to all auciton calls for this page if refresh() is called + extObj[whitelabelBidder].pv = this.getPageId(); // attach the page ID that will be common to all auction calls for this page if refresh() is called let ozOmpFloorDollars = this.getWhitelabelConfigItem('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') logInfo(`${whitelabelPrefix}_omp_floor dollar value = `, ozOmpFloorDollars); if (typeof ozOmpFloorDollars === 'number') { @@ -323,25 +313,22 @@ export const spec = { let ozWhitelistAdserverKeys = this.getWhitelabelConfigItem('ozone.oz_whitelist_adserver_keys'); let useOzWhitelistAdserverKeys = isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; extObj[whitelabelBidder][whitelabelPrefix + '_kvp_rw'] = useOzWhitelistAdserverKeys ? 1 : 0; - if (whitelabelBidder != 'ozone') { + if (whitelabelBidder !== 'ozone') { logInfo('setting aliases object'); extObj.prebid = {aliases: {'ozone': whitelabelBidder}}; } - if (getParams.hasOwnProperty('ozf')) { extObj[whitelabelBidder]['ozf'] = getParams.ozf == 'true' || getParams.ozf == 1 ? 1 : 0; } - if (getParams.hasOwnProperty('ozpf')) { extObj[whitelabelBidder]['ozpf'] = getParams.ozpf == 'true' || getParams.ozpf == 1 ? 1 : 0; } + if (getParams.hasOwnProperty('ozf')) { extObj[whitelabelBidder]['ozf'] = getParams.ozf === 'true' || getParams.ozf === '1' ? 1 : 0; } + if (getParams.hasOwnProperty('ozpf')) { extObj[whitelabelBidder]['ozpf'] = getParams.ozpf === 'true' || getParams.ozpf === '1' ? 1 : 0; } if (getParams.hasOwnProperty('ozrp') && getParams.ozrp.match(/^[0-3]$/)) { extObj[whitelabelBidder]['ozrp'] = parseInt(getParams.ozrp); } if (getParams.hasOwnProperty('ozip') && getParams.ozip.match(/^\d+$/)) { extObj[whitelabelBidder]['ozip'] = parseInt(getParams.ozip); } if (this.propertyBag.endpointOverride != null) { extObj[whitelabelBidder]['origin'] = this.propertyBag.endpointOverride; } - - var userExtEids = this.generateEids(validBidRequests); // generate the UserIDs in the correct format for UserId module - + let userExtEids = deepAccess(validBidRequests, '0.userIdAsEids', []); // generate the UserIDs in the correct format for UserId module ozoneRequest.site = { 'publisher': {'id': htmlParams.publisherId}, - 'page': document.location.href, + 'page': getRefererInfo().page, 'id': htmlParams.siteId }; - ozoneRequest.test = (getParams.hasOwnProperty('pbjs_debug') && getParams['pbjs_debug'] === 'true') ? 1 : 0; - + ozoneRequest.test = config.getConfig('debug') ? 1 : 0; if (bidderRequest && bidderRequest.gdprConsent) { logInfo('ADDING GDPR info'); let apiVersion = deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1); @@ -355,20 +342,18 @@ export const spec = { logInfo('WILL NOT ADD GDPR info; no bidderRequest.gdprConsent object'); } if (bidderRequest && bidderRequest.uspConsent) { - logInfo('ADDING CCPA info'); - deepSetValue(ozoneRequest, 'user.ext.uspConsent', bidderRequest.uspConsent); + logInfo('ADDING USP consent info'); + deepSetValue(ozoneRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); } else { - logInfo('WILL NOT ADD CCPA info; no bidderRequest.uspConsent.'); + logInfo('WILL NOT ADD USP consent info; no bidderRequest.uspConsent.'); } if (schain) { // we set this while iterating over the bids logInfo('schain found'); deepSetValue(ozoneRequest, 'source.ext.schain', schain); } - if (config.getConfig('coppa') === true) { deepSetValue(ozoneRequest, 'regs.coppa', 1); } - if (singleRequest) { logInfo('buildRequests starting to generate response for a single request'); ozoneRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. @@ -410,19 +395,6 @@ export const spec = { logInfo(`buildRequests going to return for non-single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, arrRet); return arrRet; }, - /** - * parse a bidRequestRef that contains getFloor(), get all the data from it for the sizes & media requested for this bid & return an object containing floor data you can send to auciton endpoint - * @param bidRequestRef object = a valid bid request object reference - * @return object - * - * call: - * bidObj.getFloor({ - currency: 'USD', <- currency to return the value in - mediaType: ‘banner’, - size: ‘*’ <- or [300,250] or [[300,250],[640,480]] - * }); - * - */ getFloorObjectForAuction(bidRequestRef) { const mediaTypesSizes = { banner: deepAccess(bidRequestRef, 'mediaTypes.banner.sizes', null), @@ -443,16 +415,6 @@ export const spec = { logInfo('getFloorObjectForAuction returning : ', JSON.parse(JSON.stringify(ret))); return ret; }, - /** - * Interpret the response if the array contains BIDDER elements, in the format: [ [bidder1 bid 1, bidder1 bid 2], [bidder2 bid 1, bidder2 bid 2] ] - * NOte that in singleRequest mode this will be called once, else it will be called for each adSlot's response - * - * Updated April 2019 to return all bids, not just the one we decide is the 'winner' - * - * @param serverResponse - * @param request - * @returns {*} - */ interpretResponse(serverResponse, request) { if (request && request.bidderRequest && request.bidderRequest.bids) { this.loadWhitelabelData(request.bidderRequest.bids[0]); } let startTime = new Date().getTime(); @@ -474,15 +436,12 @@ export const spec = { enhancedAdserverTargeting = true; } logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); - serverResponse.seatbid = injectAdIdsIntoAllBidResponses(serverResponse.seatbid); // we now make sure that each bid in the bidresponse has a unique (within page) adId attribute. - serverResponse.seatbid = this.removeSingleBidderMultipleBids(serverResponse.seatbid); let ozOmpFloorDollars = this.getWhitelabelConfigItem('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') let addOzOmpFloorDollars = typeof ozOmpFloorDollars === 'number'; let ozWhitelistAdserverKeys = this.getWhitelabelConfigItem('ozone.oz_whitelist_adserver_keys'); let useOzWhitelistAdserverKeys = isArray(ozWhitelistAdserverKeys) && ozWhitelistAdserverKeys.length > 0; - for (let i = 0; i < serverResponse.seatbid.length; i++) { let sb = serverResponse.seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { @@ -495,17 +454,30 @@ export const spec = { let isVideo = false; let bidType = deepAccess(thisBid, 'ext.prebid.type'); logInfo(`this bid type is : ${bidType}`, j); + let adserverTargeting = {}; if (bidType === VIDEO) { isVideo = true; + this.setBidMediaTypeIfNotExist(thisBid, VIDEO); videoContext = this.getVideoContextForBidId(thisBid.bidId, request.bidderRequest.bids); // should be instream or outstream (or null if error) if (videoContext === 'outstream') { - logInfo('going to attach a renderer to OUTSTREAM video : ', j); + logInfo('going to set thisBid.mediaType = VIDEO & attach a renderer to OUTSTREAM video : ', j); thisBid.renderer = newRenderer(thisBid.bidId); } else { - logInfo('bid is not an outstream video, will not attach a renderer: ', j); + logInfo('bid is not an outstream video, will set thisBid.mediaType = VIDEO and thisBid.vastUrl and not attach a renderer: ', j); + thisBid.vastUrl = `https://${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_host', 'missing_host')}${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_path', 'missing_path')}?id=${deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_id', 'missing_id')}`; // need to see if this works ok for ozone + adserverTargeting['hb_cache_host'] = deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_host', 'no-host'); + adserverTargeting['hb_cache_path'] = deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_path', 'no-path'); + if (!thisBid.hasOwnProperty('videoCacheKey')) { + let videoCacheUuid = deepAccess(thisBid, 'ext.prebid.targeting.hb_uuid', 'no_hb_uuid'); + logInfo(`Adding videoCacheKey: ${videoCacheUuid}`); + thisBid.videoCacheKey = videoCacheUuid; + } else { + logInfo('videoCacheKey already exists on the bid object, will not add it'); + } } + } else { + this.setBidMediaTypeIfNotExist(thisBid, BANNER); } - let adserverTargeting = {}; if (enhancedAdserverTargeting) { let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); @@ -548,7 +520,8 @@ export const spec = { adserverTargeting[whitelabelPrefix + '_auc_id'] = String(request.bidderRequest.auctionId); adserverTargeting[whitelabelPrefix + '_winner'] = String(winningSeat); adserverTargeting[whitelabelPrefix + '_bid'] = 'true'; - + adserverTargeting[whitelabelPrefix + '_cache_id'] = deepAccess(thisBid, 'ext.prebid.targeting.hb_cache_id', 'no-id'); + adserverTargeting[whitelabelPrefix + '_uuid'] = deepAccess(thisBid, 'ext.prebid.targeting.hb_uuid', 'no-id'); if (enhancedAdserverTargeting) { adserverTargeting[whitelabelPrefix + '_imp_id'] = String(winningBid.impid); adserverTargeting[whitelabelPrefix + '_pb_v'] = OZONEVERSION; @@ -566,26 +539,24 @@ export const spec = { } } let endTime = new Date().getTime(); - logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`, arrAllBids); + logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`); + logInfo('interpretResponse arrAllBids (serialised): ', JSON.parse(JSON.stringify(arrAllBids))); // this is ok to log because the renderer has not been attached yet return arrAllBids; }, - /** - * Use this to get all config values - * Now it's getting complicated with whitelabeling, this simplifies the code for getting config values. - * eg. to get whitelabelled version you just sent the ozone default string like ozone.oz_omp_floor - * @param ozoneVersion string like 'ozone.oz_omp_floor' - * @return {string|object} - */ + setBidMediaTypeIfNotExist(thisBid, mediaType) { + if (!thisBid.hasOwnProperty('mediaType')) { + logInfo(`setting thisBid.mediaType = ${mediaType}`); + thisBid.mediaType = mediaType; + } else { + logInfo(`found value for thisBid.mediaType: ${thisBid.mediaType}`); + } + }, getWhitelabelConfigItem(ozoneVersion) { - if (this.propertyBag.whitelabel.bidder == 'ozone') { return config.getConfig(ozoneVersion); } + if (this.propertyBag.whitelabel.bidder === 'ozone') { return config.getConfig(ozoneVersion); } let whitelabelledSearch = ozoneVersion.replace('ozone', this.propertyBag.whitelabel.bidder); whitelabelledSearch = whitelabelledSearch.replace('oz_', this.propertyBag.whitelabel.keyPrefix + '_'); return config.getConfig(whitelabelledSearch); }, - /** - * If a bidder bids for > 1 size for an adslot, allow only the highest bid - * @param seatbid object (serverResponse.seatbid) - */ removeSingleBidderMultipleBids(seatbid) { var ret = []; for (let i = 0; i < seatbid.length; i++) { @@ -616,7 +587,7 @@ export const spec = { } if (optionsType.iframeEnabled) { var arrQueryString = []; - if (document.location.search.match(/pbjs_debug=true/)) { + if (config.getConfig('debug')) { arrQueryString.push('pbjs_debug=true'); } arrQueryString.push('gdpr=' + (deepAccess(gdprConsent, 'gdprApplies', false) ? '1' : '0')); @@ -629,7 +600,6 @@ export const spec = { arrQueryString.push('siteId=' + this.cookieSyncBag.siteId); arrQueryString.push('cb=' + Date.now()); arrQueryString.push('bidder=' + this.propertyBag.whitelabel.bidder); - var strQueryString = arrQueryString.join('&'); if (strQueryString.length > 0) { strQueryString = '?' + strQueryString; @@ -641,11 +611,6 @@ export const spec = { }]; } }, - /** - * Find the bid matching the bidId in the request object - * get instream or outstream if this was a video request else null - * @return object|null - */ getBidRequestForBidId(bidId, arrBids) { for (let i = 0; i < arrBids.length; i++) { if (arrBids[i].bidId === bidId) { // bidId in the request comes back as impid in the seatbid bids @@ -654,13 +619,6 @@ export const spec = { } return null; }, - /** - * Locate the bid inside the arrBids for this bidId, then discover the video context, and return it. - * IF the bid cannot be found return null, else return a string. - * @param bidId - * @param arrBids - * @return string|null - */ getVideoContextForBidId(bidId, arrBids) { let requestBid = this.getBidRequestForBidId(bidId, arrBids); if (requestBid != null) { @@ -668,11 +626,6 @@ export const spec = { } return null; }, - /** - * This is used for cookie sync, not auction call - * Look for pubcid & all the other IDs according to http://prebid.org/dev-docs/modules/userId.html - * @return map - */ findAllUserIds(bidRequest) { var ret = {}; let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; @@ -706,10 +659,6 @@ export const spec = { if (sharedid) { ret['sharedid'] = sharedid; } - let sharedidthird = deepAccess(bidRequest.userId, 'sharedid.third'); - if (sharedidthird) { - ret['sharedidthird'] = sharedidthird; - } } if (!ret.hasOwnProperty('pubcid')) { let pubcid = deepAccess(bidRequest, 'crumbs.pubcid'); @@ -719,20 +668,9 @@ export const spec = { } return ret; }, - /** - * Convenient method to get the value we need for the placementId - ONLY from the bidRequest - NOT taking into account any GET override ID - * @param bidRequest - * @return string - */ getPlacementId(bidRequest) { return (bidRequest.params.placementId).toString(); }, - /** - * GET parameter introduced in 2.2.0 : ozstoredrequest - * IF the GET parameter exists then it must validate for placementId correctly - * IF there's a $_GET['ozstoredrequest'] & it's valid then return this. Else return null. - * @returns null|string - */ getPlacementIdOverrideFromGetParam() { let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; let arr = this.getGetParametersAsObject(); @@ -746,68 +684,19 @@ export const spec = { } return null; }, - /** - * Generate an object we can append to the auction request, containing user data formatted correctly for different ssps - * http://prebid.org/dev-docs/modules/userId.html - * @param validBidRequests - * @return {Array} - */ - generateEids(validBidRequests) { - let eids; - const bidRequest = validBidRequests[0]; - if (bidRequest && bidRequest.userId) { - eids = bidRequest.userIdAsEids; - this.handleTTDId(eids, validBidRequests); - } - return eids; - }, - handleTTDId(eids, validBidRequests) { - let ttdId = null; - let adsrvrOrgId = config.getConfig('adsrvrOrgId'); - if (isStr(deepAccess(validBidRequests, '0.userId.tdid'))) { - ttdId = validBidRequests[0].userId.tdid; - } else if (adsrvrOrgId && isStr(adsrvrOrgId.TDID)) { - ttdId = adsrvrOrgId.TDID; - } - if (ttdId !== null) { - eids.push({ - 'source': 'adserver.org', - 'uids': [{ - 'id': ttdId, - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }); - } - }, getGetParametersAsObject() { - let items = location.search.substr(1).split('&'); - let ret = {}; - let tmp = null; - for (let index = 0; index < items.length; index++) { - tmp = items[index].split('='); - ret[tmp[0]] = tmp[1]; - } - return ret; + let parsed = parseUrl(getRefererInfo().page); + logInfo('getGetParametersAsObject found:', parsed.search); + return parsed.search; }, - /** - * Do we have to block this request? Could be due to config values (no longer checking gdpr) - * @return {boolean|*[]} true = block the request, else false - */ blockTheRequest() { let ozRequest = this.getWhitelabelConfigItem('ozone.oz_request'); if (typeof ozRequest == 'boolean' && !ozRequest) { - logWarn(`Will not allow auction : ${this.propertyBag.whitelabel.keyPrefix}one.${this.propertyBag.whitelabel.keyPrefix}_request is set to false`); + logWarn(`Will not allow auction : ${this.propertyBag.whitelabel.keyPrefix}_request is set to false`); return true; } return false; }, - /** - * This returns a random ID for this page. It starts off with the current ms timestamp then appends a random component - * @return {string} - */ getPageId: function() { if (this.propertyBag.pageId == null) { let randPart = ''; @@ -825,14 +714,6 @@ export const spec = { ret = this._unpackVideoConfigIntoIABformat(ret, childConfig); return ret; }, - /** - * - * look in ONE object to get video config (we need to call this multiple times, so child settings override parent) - * @param ret - * @param objConfig - * @return {*} - * @private - */ _unpackVideoConfigIntoIABformat(ret, objConfig) { let arrVideoKeysAllowed = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype']; for (const key in objConfig) { @@ -861,14 +742,6 @@ export const spec = { objRet = this._addVideoDefaults(objRet, childConfig, true); // child config will override parent config return objRet; }, - /** - * modify objRet, adding in default values - * @param objRet - * @param objConfig - * @param addIfMissing - * @return {*} - * @private - */ _addVideoDefaults(objRet, objConfig, addIfMissing) { let context = deepAccess(objConfig, 'context'); if (context === 'outstream') { @@ -885,15 +758,38 @@ export const spec = { objRet.skip = skippable ? 1 : 0; } return objRet; + }, + getLoggableBidObject(bid) { + let logObj = { + ad: bid.ad, + adId: bid.adId, + adUnitCode: bid.adUnitCode, + adm: bid.adm, + adomain: bid.adomain, + adserverTargeting: bid.adserverTargeting, + auctionId: bid.auctionId, + bidId: bid.bidId, + bidder: bid.bidder, + bidderCode: bid.bidderCode, + cpm: bid.cpm, + creativeId: bid.creativeId, + crid: bid.crid, + currency: bid.currency, + h: bid.h, + w: bid.w, + impid: bid.impid, + mediaType: bid.mediaType, + params: bid.params, + price: bid.price, + transactionId: bid.transactionId, + ttl: bid.ttl + }; + if (bid.hasOwnProperty('floorData')) { + logObj.floorData = bid.floorData; + } + return logObj; } }; - -/** - * add a page-level-unique adId element to all server response bids. - * NOTE that this is destructive - it mutates the serverResponse object sent in as a parameter - * @param seatbid object (serverResponse.seatbid) - * @returns seatbid object - */ export function injectAdIdsIntoAllBidResponses(seatbid) { logInfo('injectAdIdsIntoAllBidResponses', seatbid); for (let i = 0; i < seatbid.length; i++) { @@ -904,7 +800,6 @@ export function injectAdIdsIntoAllBidResponses(seatbid) { } return seatbid; } - export function checkDeepArray(Arr) { if (Array.isArray(Arr)) { if (Array.isArray(Arr[0])) { @@ -916,7 +811,6 @@ export function checkDeepArray(Arr) { return Arr; } } - export function defaultSize(thebidObj) { if (!thebidObj) { logInfo('defaultSize received empty bid obj! going to return fixed default size'); @@ -931,13 +825,6 @@ export function defaultSize(thebidObj) { returnObject.defaultHeight = checkDeepArray(sizes)[1]; return returnObject; } - -/** - * Do the messy searching for the best bid response in the serverResponse.seatbid array matching the requestBid.bidId - * @param requestBid - * @param serverResponseSeatBid - * @returns {*} bid object - */ export function ozoneGetWinnerForRequestBid(requestBidId, serverResponseSeatBid) { let thisBidWinner = null; let winningSeat = null; @@ -956,13 +843,6 @@ export function ozoneGetWinnerForRequestBid(requestBidId, serverResponseSeatBid) } return {'seat': winningSeat, 'bid': thisBidWinner}; } - -/** - * Get a list of all the bids, for this bidId. The keys in the response object will be {seatname} OR {seatname}{w}x{h} if seatname already exists - * @param matchBidId - * @param serverResponseSeatBid - * @returns {} = {ozone|320x600:{obj}, ozone|320x250:{obj}, appnexus|300x250:{obj}, ... } - */ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { let objBids = {}; for (let j = 0; j < serverResponseSeatBid.length; j++) { @@ -982,21 +862,13 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { } return objBids; } - -/** - * Round the bid price down according to the granularity - * @param price - * @param mediaType = video, banner or native - */ export function getRoundedBid(price, mediaType) { const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`); // might be string or object or nothing; if set then this takes precedence over 'priceGranularity' let objBuckets = config.getConfig('customPriceBucket'); // this is always an object - {} if strBuckets is not 'custom' let strBuckets = config.getConfig('priceGranularity'); // priceGranularity value, always a string ** if priceGranularity is set to an object then it's always 'custom' ** let theConfigObject = getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets); let theConfigKey = getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets); - logInfo('getRoundedBid. price:', price, 'mediaType:', mediaType, 'configkey:', theConfigKey, 'configObject:', theConfigObject, 'mediaTypeGranularity:', mediaTypeGranularity, 'strBuckets:', strBuckets); - let priceStringsObj = getPriceBucketString( price, theConfigObject, @@ -1017,13 +889,6 @@ export function getRoundedBid(price, mediaType) { } return priceStringsObj['auto']; } - -/** - * return the key to use to get the value out of the priceStrings object, taking into account anything at - * config.priceGranularity level or config.mediaType.xxx level - * I've noticed that the key specified by prebid core : config.getConfig('priceGranularity') does not properly - * take into account the 2-levels of config - */ export function getGranularityKeyName(mediaType, mediaTypeGranularity, strBuckets) { if (typeof mediaTypeGranularity === 'string') { return mediaTypeGranularity; @@ -1036,11 +901,6 @@ export function getGranularityKeyName(mediaType, mediaTypeGranularity, strBucket } return 'auto'; // fall back to a default key - should literally never be needed. } - -/** - * return the object to use to create the custom value of the priceStrings object, taking into account anything at - * config.priceGranularity level or config.mediaType.xxx level - */ export function getGranularityObject(mediaType, mediaTypeGranularity, strBuckets, objBuckets) { if (typeof mediaTypeGranularity === 'object') { return mediaTypeGranularity; @@ -1050,12 +910,6 @@ export function getGranularityObject(mediaType, mediaTypeGranularity, strBuckets } return ''; } - -/** - * We expect to be able to find a standard set of properties on winning bid objects; add them here. - * @param seatBid - * @returns {*} - */ export function ozoneAddStandardProperties(seatBid, defaultWidth, defaultHeight) { seatBid.cpm = seatBid.price; seatBid.bidId = seatBid.impid; @@ -1069,12 +923,6 @@ export function ozoneAddStandardProperties(seatBid, defaultWidth, defaultHeight) seatBid.ttl = 300; return seatBid; } - -/** - * - * @param objVideo will be like {"playerSize":[640,480],"mimes":["video/mp4"],"context":"outstream"} or POSSIBLY {"playerSize":[[640,480]],"mimes":["video/mp4"],"context":"outstream"} - * @return object {w,h} or null - */ export function getWidthAndHeightFromVideoObject(objVideo) { let playerSize = getPlayerSizeFromObject(objVideo); if (!playerSize) { @@ -1094,11 +942,6 @@ export function getWidthAndHeightFromVideoObject(objVideo) { } return ({'w': playerSize[0], 'h': playerSize[1]}); } - -/** - * @param objVideo will be like {"playerSize":[640,480],"mimes":["video/mp4"],"context":"outstream"} or POSSIBLY {"playerSize":[[640,480]],"mimes":["video/mp4"],"context":"outstream"} - * @return object {w,h} or null - */ export function playerSizeIsNestedArray(objVideo) { let playerSize = getPlayerSizeFromObject(objVideo); if (!playerSize) { @@ -1109,12 +952,6 @@ export function playerSizeIsNestedArray(objVideo) { } return (playerSize[0] && typeof playerSize[0] === 'object'); } - -/** - * Common functionality when looking at a video object, to get the playerSize - * @param objVideo - * @returns {*} - */ function getPlayerSizeFromObject(objVideo) { logInfo('getPlayerSizeFromObject received object', objVideo); let playerSize = deepAccess(objVideo, 'playerSize'); @@ -1131,10 +968,6 @@ function getPlayerSizeFromObject(objVideo) { } return playerSize; } -/* - Rendering video ads - create a renderer instance, mark it as not loaded, set a renderer function. - The renderer function will not assume that the renderer script is loaded - it will push() the ultimate render function call - */ function newRenderer(adUnitCode, rendererOptions = {}) { let isLoaded = window.ozoneVideo; logInfo(`newRenderer going to set loaded to ${isLoaded ? 'true' : 'false'}`); @@ -1147,16 +980,18 @@ function newRenderer(adUnitCode, rendererOptions = {}) { try { renderer.setRender(outstreamRender); } catch (err) { - logError('Prebid Error when calling setRender on renderer', JSON.parse(JSON.stringify(renderer)), err); + logError('Prebid Error when calling setRender on renderer', renderer, err); } + logInfo('returning renderer object'); return renderer; } function outstreamRender(bid) { - logInfo('outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid =', JSON.parse(JSON.stringify(bid))); + logInfo('outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid = (first static, then reference)'); + logInfo(JSON.parse(JSON.stringify(spec.getLoggableBidObject(bid)))); bid.renderer.push(() => { + logInfo('Going to execute window.ozoneVideo.outstreamRender'); window.ozoneVideo.outstreamRender(bid); }); } - registerBidder(spec); logInfo(`*BidAdapter ${OZONEVERSION} was loaded`); diff --git a/modules/ozoneBidAdapter.md b/modules/ozoneBidAdapter.md index ca18c962219..6f4cf752f22 100644 --- a/modules/ozoneBidAdapter.md +++ b/modules/ozoneBidAdapter.md @@ -72,3 +72,40 @@ adUnits = [{ }] }]; ``` + +//Instream Video adUnit + +adUnits = [{ + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2] + } + }, + bids: [{ + bidder: 'ozone', + params: { + publisherId: 'OZONENUK0001', + placementId: '8000000328', // or 999 + siteId: '4204204201', + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + }, + customData: [{ + "settings": {}, + "targeting": { + "key": "value", + "key2": ["value1", "value2"] + } + } + ] + + } + }] + }; +``` diff --git a/modules/padsquadBidAdapter.js b/modules/padsquadBidAdapter.js index 72449cf28be..d5fe37ef34b 100644 --- a/modules/padsquadBidAdapter.js +++ b/modules/padsquadBidAdapter.js @@ -43,9 +43,9 @@ export const spec = { id: bidderRequest.auctionId, imp: impressions, site: { - domain: window.location.hostname, - page: window.location.href, - ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null + domain: bidderRequest?.refererInfo?.domain, + page: bidderRequest?.refererInfo?.page, + ref: bidderRequest?.refererInfo?.ref, }, ext: { exchange: { diff --git a/modules/papyrusBidAdapter.md b/modules/papyrusBidAdapter.md deleted file mode 100644 index 98a42e542ec..00000000000 --- a/modules/papyrusBidAdapter.md +++ /dev/null @@ -1,41 +0,0 @@ -# Overview - -``` -Module Name: Papyrus Bid Adapter -Module Type: Bidder Adapter -Maintainer: alexander.holodov@papyrus.global -``` - -# Description - -Connect to Papyrus system for bids. - -Papyrus bid adapter supports Banner. - -Please contact to info@papyrus.global for -further details - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [ - [320, 50] - ] - } - }, - bids: [ - { - bidder: 'papyrus', - params: { - address: '0xd7e2a771c5dcd5df7f789477356aecdaeee6c985', - placementId: 'b57e55fd18614b0591893e9fff41fbea' - } - } - ] - } - ]; -``` diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 04f36d0cb63..4a777097914 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -244,7 +244,7 @@ function fetchId(configParams, gdprConsentData) { const data = { eid, trackers, - url: refererInfo.referer, + url: refererInfo.page, prebidVersion: '$prebid.version$', isIframe: inIframe(), tpcSupport diff --git a/modules/peak226BidAdapter.md b/modules/peak226BidAdapter.md deleted file mode 100644 index bae15d6c99f..00000000000 --- a/modules/peak226BidAdapter.md +++ /dev/null @@ -1,31 +0,0 @@ -# Overview - -``` -Module Name: Peak226 Bidder Adapter -Module Type: Bidder Adapter -Maintainer: support@edge226.com -``` - -# Description - -Module that connects to Peak226's demand sources - -# Test Parameters - -``` - var adUnits = [ - { - code: "test-div", - sizes: [[300, 250]], - mediaType: "banner", - bids: [ - { - bidder: "peak226", - params: { - uid: 76131369 - } - } - ] - } - ]; -``` diff --git a/modules/performaxBidAdapter.md b/modules/performaxBidAdapter.md deleted file mode 100644 index 4cf2984a79d..00000000000 --- a/modules/performaxBidAdapter.md +++ /dev/null @@ -1,36 +0,0 @@ -# Overview - -``` -Module Name: Performax Bid Adapter -Module Type: Bidder Adapter -Maintainer: development@performax.cz -``` - -# Description - -Connects to Performax exchange for bids. - -Performax bid adapter supports Banner. - - -# Sample Banner Ad Unit: For Publishers - -```javascript - var adUnits = [ - { - code: 'performax-div', - sizes: [[300, 300]], - bids: [ - { - bidder: "performax", - params: { - slotId: 28 // required - } - } - ] - } - ]; -``` - -Where: -* slotId - id of slot in PX system diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index c4674132416..a2cd7847564 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -8,15 +8,18 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {submodule} from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; -import {deepAccess, deepSetValue, isFn, logError, mergeDeep} from '../src/utils.js'; -import {config} from '../src/config.js'; +import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safeJSONParse} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; const MODULE_NAME = 'permutive' +export const PERMUTIVE_SUBMODULE_CONFIG_KEY = 'permutive-prebid-rtd' + export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) -function init (moduleConfig, userConsent) { +function init(moduleConfig, userConsent) { + readPermutiveModuleConfigFromCache() + return true } @@ -44,29 +47,71 @@ export function initSegments (reqBidsConfigObj, callback, customModuleConfig) { } } +function liftIntoParams(params) { + return isPlainObject(params) ? { params } : {} +} + +let cachedPermutiveModuleConfig = {} + /** - * Merges segments into existing bidder config + * Access the submodules RTD params that are cached to LocalStorage by the Permutive SDK. This lets the RTD submodule + * apply publisher defined params set in the Permutive platform, so they may still be applied if the Permutive SDK has + * not initialised before this submodule is initialised. + */ +function readPermutiveModuleConfigFromCache() { + const params = safeJSONParse(storage.getDataFromLocalStorage(PERMUTIVE_SUBMODULE_CONFIG_KEY)) + return cachedPermutiveModuleConfig = liftIntoParams(params) +} + +/** + * Access the submodules RTD params attached to the Permutive SDK. + * + * @return The Permutive config available by the Permutive SDK or null if the operation errors. + */ +function getParamsFromPermutive() { + try { + return liftIntoParams(window.permutive.addons.prebid.getPermutiveRtdConfig()) + } catch (e) { + return null + } +} + +/** + * Merges segments into existing bidder config in reverse priority order. The highest priority is 1. + * + * 1. customModuleConfig <- set by publisher with pbjs.setConfig + * 2. permutiveRtdConfig <- set by the publisher using the Permutive platform + * 3. defaultConfig + * + * As items with a higher priority will be deeply merged into the previous config, deep merges are performed by + * reversing the priority order. + * * @param {Object} customModuleConfig - Publisher config for module - * @return {Object} Merged defatul and custom config + * @return {Object} Deep merges of the default, Permutive and custom config. */ -function getModuleConfig (customModuleConfig) { +export function getModuleConfig(customModuleConfig) { + // Use the params from Permutive if available, otherwise fallback to the cached value set by Permutive. + const permutiveModuleConfig = getParamsFromPermutive() || cachedPermutiveModuleConfig + return mergeDeep({ waitForIt: false, params: { maxSegs: 500, acBidders: [], - overwrites: {} - } - }, customModuleConfig) + overwrites: {}, + }, + }, + permutiveModuleConfig, + customModuleConfig, + ) } /** * Sets ortb2 config for ac bidders - * @param {Object} auctionDetails + * @param {Object} bidderOrtb2 * @param {Object} customModuleConfig - Publisher config for module */ -export function setBidderRtb (auctionDetails, customModuleConfig) { - const bidderConfig = config.getBidderConfig() +export function setBidderRtb (bidderOrtb2, customModuleConfig) { const moduleConfig = getModuleConfig(customModuleConfig) const acBidders = deepAccess(moduleConfig, 'params.acBidders') const maxSegs = deepAccess(moduleConfig, 'params.maxSegs') @@ -74,13 +119,9 @@ export function setBidderRtb (auctionDetails, customModuleConfig) { const segmentData = getSegments(maxSegs) acBidders.forEach(function (bidder) { - const currConfig = bidderConfig[bidder] || {} + const currConfig = { ortb2: bidderOrtb2[bidder] || {} } const nextConfig = updateOrtbConfig(currConfig, segmentData.ac, transformationConfigs) // ORTB2 uses the `ac` segment IDs - - config.setBidderConfig({ - bidders: [bidder], - config: nextConfig - }) + bidderOrtb2[bidder] = nextConfig.ortb2; }) } @@ -306,12 +347,10 @@ export const permutiveSubmodule = { makeSafe(function () { // Legacy route with custom parameters initSegments(reqBidsConfigObj, callback, customModuleConfig) - }) - }, - onAuctionInitEvent: function (auctionDetails, customModuleConfig) { + }); makeSafe(function () { // Route for bidders supporting ORTB2 - setBidderRtb(auctionDetails, customModuleConfig) + setBidderRtb(reqBidsConfigObj.ortb2Fragments?.bidder, customModuleConfig) }) }, init: init diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 5fa6e14a474..f99389f82cc 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -1,9 +1,10 @@ -# Permutive Real-time Data Submodule +## Prebid Config for Permutive RTD Module -This submodule reads cohorts from Permutive and attaches them as targeting keys to bid requests. Using this module will deliver best targeting results, leveraging Permutive's real-time segmentation and modelling capabilities. +This module reads cohorts from Permutive and attaches them as targeting keys to bid requests. -## Usage +### _Permutive Real-time Data Submodule_ +#### Usage Compile the Permutive RTD module into your Prebid build: ``` @@ -31,26 +32,13 @@ pbjs.setConfig({ }) ``` -## Supported Bidders +#### Parameters -The Permutive RTD module sets Audience Connector cohorts as bidder-specific `ortb2.user.data` first-party data, following the Prebid `ortb2` convention, for any bidder included in `acBidders`. The module also supports bidder-specific data locations per ad unit (custom parameters) for the below bidders: - -| Bidder | ID | Custom Cohorts | Audience Connector | -| ------- | ---------- | -------------- | ------------------ | -| Xandr | `appnexus` | Yes | Yes | -| Magnite | `rubicon` | Yes | No | -| Ozone | `ozone` | No | Yes | - -Key-values details for custom parameters: - -- **Custom Cohorts:** When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. Permutive cohorts will be sent in the `permutive` key-value. - -- **Audience Connector:** You'll need to define which bidders should receive Audience Connector cohorts. You need to include the `ID` of any bidder in the `acBidders` array. Audience Connector cohorts will be sent in the `p_standard` key-value. - -## Parameters +The parameters below provide configurability for general behaviours of the RTD submodule, +as well as enabling settings for specific use cases mentioned above (e.g. acbidders). | Name | Type | Description | Default | -| ---------------------- | -------- | --------------------------------------------------------------------------------------------- | ------- | +|------------------------|----------|-----------------------------------------------------------------------------------------------|---------| | name | String | This should always be `permutive` | - | | waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | | params | Object | | - | @@ -58,12 +46,103 @@ Key-values details for custom parameters: | params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | | params.transformations | Object[] | An array of configurations for ORTB2 user data transformations | | -### The `transformations` parameter +##### The `transformations` parameter This array contains configurations for transformations we'll apply to the Permutive object in the ORTB2 `user.data` array. The results of these transformations will be appended to the `user.data` array that's attached to ORTB2 bid requests. -#### Supported transformations +##### Supported transformations | Name | ID | Config structure | Description | -| -------------- | --- | ------------------------------------------------- | ------------------------------------------------------------------------------------ | +|----------------|-----|---------------------------------------------------|--------------------------------------------------------------------------------------| | IAB taxonomies | iab | { segtax: number, iabIds: Object} | Transform segment IDs from Permutive to IAB (note: alpha version, subject to change) | + +#### Context + +Permutive is not listed as a TCF vendor as all data collection is on behalf of the publisher and based on consent the publisher has received from the user. +Rather than through the TCF framework, this consent is provided to Permutive when the user gives the relevant permissions on the publisher website which allow the Permutive SDK to run. +This means that if GDPR enforcement is configured _and_ the user consent isn’t given for Permutive to fire, no cohorts will populate. +As Prebid utilizes TCF vendor consent, for the Permutive RTD module to load, Permutive needs to be labeled within the Vendor Exceptions + +#### Instructions + +1. Publisher enables GDPR rules within Prebid. +2. Label Permutive as an exception, as shown below. +```javascript +[ + { + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: ["permutive"] + }, + { + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + } +] +``` + +Before making any updates to this configuration, please ensure that this approach aligns with internal policies and current regulations regarding consent. + +## Cohort Activation with Permutive RTD Module + +### _Enabling Standard Cohorts_ + +**Note**: Publishers must be enabled on the above Permutive RTD Submodule to enable Standard Cohorts. + +The acbidders config in the Permutive RTD module allows publishers to determine which demand partners (SSPs) will receive standard cohorts via the user.data ortb2 object. Cohorts will be sent in the `p_standard` key-value. + +The Permutive RTD module sets standard cohort IDs as bidder-specific ortb2.user.data first-party data, following the Prebid ortb2 convention. + +There are **two** ways to assign which demand partner bidders (e.g. SSPs) will receive Standard Cohort information via the Audience Connector (acbidders) config: + +#### Option 1 - Automated + +New demand partner bidders may be added to the acbidders config directly within the Permutive Platform. + +**Permutive can do this on your behalf**. Simply contact your Permutive CSM with strategicpartnershipops@permutive.com on cc, +indicating which bidders you would like added. + +Or, a publisher may do this themselves within the UI using the below instructions. + +##### Create Integration + +In order to update acbidders via the Permutive dashboard, +it is necessary to first enable the prebid integration in the integrations page (settings). + +**Note on Revenue Insights:** The prebid integration includes a feature for revenue insights, +which is not required for the purpose of updating acbidders config. +Please see [this document](https://support.permutive.com/hc/en-us/articles/360019044079-Revenue-Insights) for more information about revenue insights. + +##### Update acbidders + +The input for the “Data Provider config” is currently a multi-input free text. +A valid “bidder code” needs to be entered in order to enable Standard Cohorts to be passed to the desired partner. +The [prebid Bidders page](https://docs.prebid.org/dev-docs/bidders.html) contains instructions and a link to a list of possible bidder codes. + +Acbidders can be added or removed from the list using this feature, however, this will not impact any acbidders that have been applied using the manual method below. + +#### Option 2 - Manual + +As a secondary option, new demand partner bidders may be added manually. + +To do so, a Publisher may define which bidders should receive Standard Cohorts by +including the _bidder code_ of any bidder in the `acBidders` array. + +**Note:** If a Publisher ever needs to remove a manually-added bidder, the bidder will also need to be removed manually. + +### _Enabling Custom Cohort IDs for Targeting_ + +Separately from Standard Cohorts - The Permutive RTD module also supports passing any of the **custom** cohorts created in the dashboard to some SSP partners for targeting +e.g. setting up publisher deals. For these activations, cohort IDs are set in bidder-specific locations per ad unit (custom parameters). + +Currently, bidders with known support for custom cohort targeting are: + +- Xandr +- Magnite + +When enabling the respective Activation for a cohort in Permutive, this module will automatically attach that cohort ID to the bid request. +There is no need to enable individual bidders in the module configuration, it will automatically reflect which SSP integrations you have enabled in your Permutive dashboard. +Permutive cohorts will be sent in the permutive key-value. diff --git a/modules/pianoDmpAnalyticcsAdapter.md b/modules/pianoDmpAnalyticcsAdapter.md new file mode 100644 index 00000000000..9bdcaaf0c7a --- /dev/null +++ b/modules/pianoDmpAnalyticcsAdapter.md @@ -0,0 +1,13 @@ +# Overview + +Module Name: Piano DMP Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: [support@piano.com](mailto:support@piano.com) + +# Description + +Analytics adapter to be used with cx.js + +Visit [https://piano.io/product/dmp/](https://piano.io/product/dmp/) for more information. diff --git a/modules/pianoDmpAnalyticsAdapter.js b/modules/pianoDmpAnalyticsAdapter.js new file mode 100644 index 00000000000..47159475b5d --- /dev/null +++ b/modules/pianoDmpAnalyticsAdapter.js @@ -0,0 +1,38 @@ +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; + +const pianoDmpAnalytics = adapter({ analyticsType: 'bundle', handler: 'on' }); + +const { enableAnalytics: _enableAnalytics } = pianoDmpAnalytics; + +Object.assign(pianoDmpAnalytics, { + /** + * Save event in the global array that will be consumed later by cx.js + */ + track: ({ eventType, args: params }) => { + window.cX.callQueue.push([ + 'prebid', + { eventType, params, time: Date.now() }, + ]); + }, + + /** + * Before forwarding the call to the original enableAnalytics function - + * create (if needed) the global array that is used to pass events to the cx.js library + * by the 'track' function above. + */ + enableAnalytics: function (...args) { + window.cX = window.cX || {}; + window.cX.callQueue = window.cX.callQueue || []; + + return _enableAnalytics.call(this, ...args); + }, +}); + +adapterManager.registerAnalyticsAdapter({ + adapter: pianoDmpAnalytics, + code: 'pianoDmp', + gvlid: 412, +}); + +export default pianoDmpAnalytics; diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index 29552ec796d..4c4935e93a4 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -14,6 +14,7 @@ import { transformBidderParamKeywords } from '../src/utils.js'; import {auctionManager} from '../src/auctionManager.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const SOURCE = 'pbjs'; const storageManager = getStorageManager({bidderCode: 'pixfuture'}); @@ -42,7 +43,7 @@ export const spec = { return validBidRequests.map((bidRequest) => { let referer = ''; if (bidderRequest && bidderRequest.refererInfo) { - referer = bidderRequest.refererInfo.referer || ''; + referer = bidderRequest.refererInfo.page || ''; } const userObjBid = find(validBidRequests, hasUserInfo); @@ -90,7 +91,8 @@ export const spec = { if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { - rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + // TODO: this collects everything it finds, except for canonicalUrl + rd_ref: encodeURIComponent(bidderRequest.refererInfo.topmostLocation), rd_top: bidderRequest.refererInfo.reachedTop, rd_ifs: bidderRequest.refererInfo.numIframes, rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') @@ -101,7 +103,6 @@ export const spec = { if (validBidRequests[0].userId) { let eids = []; - addUserId(eids, deepAccess(validBidRequests[0], `userId.flocId.id`), 'chrome.com', null); addUserId(eids, deepAccess(validBidRequests[0], `userId.criteoId`), 'criteo.com', null); addUserId(eids, deepAccess(validBidRequests[0], `userId.unifiedId`), 'thetradedesk.com', null); addUserId(eids, deepAccess(validBidRequests[0], `userId.id5Id`), 'id5.io', null); @@ -164,7 +165,7 @@ export const spec = { getUserSyncs: function (syncOptions, bid, gdprConsent) { var pixid = ''; if (typeof bid[0] === 'undefined' || bid[0] === null) { pixid = '0'; } else { pixid = bid[0].body.pix_id; } - if (syncOptions.iframeEnabled && hasPurpose1Consent({gdprConsent})) { + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { return [{ type: 'iframe', url: 'https://gosrv.pixfuture.com/cookiesync?adsync=' + gdprConsent.consentString + '&pixid=' + pixid + '&gdprconcent=' + gdprConsent.gdprApplies @@ -197,16 +198,6 @@ function newBid(serverBid, rtbBid, placementId, uuid) { return bid; } -function hasPurpose1Consent(bidderRequest) { - let result = true; - if (bidderRequest && bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { - result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); - } - } - return result; -} - // Functions related optional parameters function bidToTag(bid) { const tag = {}; @@ -220,7 +211,7 @@ function bidToTag(bid) { tag.code = bid.params.invCode; } tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.use_pmt_rule = bid.params.usePaymentRule || false; tag.prebid = true; tag.disable_psa = true; let bidFloor = getBidFloor(bid); @@ -229,6 +220,13 @@ function bidToTag(bid) { } if (bid.params.position) { tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; + } else { + let mediaTypePos = deepAccess(bid, `mediaTypes.banner.pos`) || deepAccess(bid, `mediaTypes.video.pos`); + // only support unknown, atf, and btf values for position at this time + if (mediaTypePos === 0 || mediaTypePos === 1 || mediaTypePos === 3) { + // ortb spec treats btf === 3, but our system interprets btf === 2; so converting the ortb value here for consistency + tag.position = (mediaTypePos === 3) ? 2 : mediaTypePos; + } } if (bid.params.trafficSourceCode) { tag.traffic_source_code = bid.params.trafficSourceCode; diff --git a/modules/piximediaBidAdapter.md b/modules/piximediaBidAdapter.md deleted file mode 100644 index fae014cbdff..00000000000 --- a/modules/piximediaBidAdapter.md +++ /dev/null @@ -1,25 +0,0 @@ -# Overview - -**Module Name**: Piximedia Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: contact@piximedia.fr - -# Description - -Piximedia Bidder Adapter for Prebid.js. - -# Test Parameters -``` - var adUnits = [{ - code: 'mpu', - sizes: [[300, 250]], - bids: [{ - bidder: 'piximedia', - params: { - siteId: 'PIXIMEDIA', - placementId: 'PREBID' - } - }] - }]; - -``` diff --git a/modules/platformioBidAdapter.md b/modules/platformioBidAdapter.md deleted file mode 100644 index 863e023f0d7..00000000000 --- a/modules/platformioBidAdapter.md +++ /dev/null @@ -1,86 +0,0 @@ -# Overview - -**Module Name**: Platform.io Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: siarhei.kasukhin@platform.io - -# Description - -Connects to Platform.io demand source to fetch bids. -Banner, Native, Video formats are supported. -Please use ```platformio``` as the bidder code. - -# Test Parameters -``` - var adUnits = [{ - code: 'dfp-native-div', - mediaTypes: { - native: { - title: { - required: true, - len: 75 - }, - image: { - required: true - }, - body: { - len: 200 - }, - icon: { - required: false - } - } - }, - bids: [{ - bidder: 'platformio', - params: { - pubId: '29521', - siteId: '26048', - placementId: '123', - bidFloor: '0.001', // optional - ifa: 'XXX-XXX', // optional - latitude: '40.712775', // optional - longitude: '-74.005973', // optional - } - }] - }, - { - code: 'dfp-banner-div', - mediaTypes: { - banner: { - sizes: [ - [300, 250],[300,600] - ], - } - }, - bids: [{ - bidder: 'platformio', - params: { - pubId: '29521', - siteId: '26049', - placementId: '123', - } - }] - }, - { - code: 'dfp-video-div', - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: "instream" - } - }, - bids: [{ - bidder: 'platformio', - params: { - pubId: '29521', - siteId: '26049', - placementId: '123', - video: { - skipppable: true, - } - } - }] - } - ]; -``` diff --git a/modules/playwireBidAdapter.md b/modules/playwireBidAdapter.md deleted file mode 100644 index dddb57c9bc1..00000000000 --- a/modules/playwireBidAdapter.md +++ /dev/null @@ -1,61 +0,0 @@ -# Overview - -Module Name: Playwire Bidder Adapter -Module Type: Bidder Adapter -Maintainer: grid-tech@themediagrid.com - -# Description - -Module that connects to Grid demand source to fetch bids. -The adapter is GDPR compliant and supports banner and video (instream and outstream). - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: "playwire", - params: { - uid: '1', - bidFloor: 0.5 - } - } - ] - },{ - code: 'test-div', - sizes: [[728, 90]], - bids: [ - { - bidder: "playwire", - params: { - uid: 2, - keywords: { - brandsafety: ['disaster'], - topic: ['stress', 'fear'] - } - } - } - ] - }, - { - code: 'test-div', - sizes: [[728, 90]], - mediaTypes: { video: { - context: 'instream', - playerSize: [728, 90], - mimes: ['video/mp4'] - }, - bids: [ - { - bidder: "playwire", - params: { - uid: 11 - } - } - ] - } - ]; -``` diff --git a/modules/polluxBidAdapter.md b/modules/polluxBidAdapter.md deleted file mode 100644 index 79bf84e79b9..00000000000 --- a/modules/polluxBidAdapter.md +++ /dev/null @@ -1,33 +0,0 @@ -# Overview - -**Module Name**: Pollux Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: tech@polluxnetwork.com - -# Description - -Module that connects to Pollux Network LLC demand source to fetch bids. -All bids will present CPM in EUR (Euro). - -# Test Parameters -``` - var adUnits = [{ - code: '34f724kh32', - sizes: [[300, 250]], // a single size - bids: [{ - bidder: 'pollux', - params: { - zone: '1806' // a single zone - } - }] - },{ - code: '34f789r783', - sizes: [[300, 250], [728, 90]], // multiple sizes - bids: [{ - bidder: 'pollux', - params: { - zone: '1806,276' // multiple zones, max 5 - } - }] - }]; -``` diff --git a/modules/polymorphBidAdapter.md b/modules/polymorphBidAdapter.md deleted file mode 100644 index e778b312e56..00000000000 --- a/modules/polymorphBidAdapter.md +++ /dev/null @@ -1,43 +0,0 @@ -# Overview - -``` -Module Name: Polymorph Bidder Adapter -Module Type: Bidder Adapter -Maintainer: kuldeep@getpolymorph.com -``` - -# Description - -Connects to Polymorph Demand Cloud (s2s header-bidding) - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div-1', - sizes: [[300, 250]], - bids: [ - { - bidder: "polymorph", - params: { - placementId: 'ping' - } - } - ] - },{ - code: 'test-div-2', - sizes: [[300, 250], [300,600]] - bids: [ - { - bidder: "polymorph", - params: { - placementId: 'ping', - // In case multiple ad sizes are supported, it's recommended to specify default height and width for native ad (in case native ad is chose as a winner). In case of banner or outstream ad any one of the above sizes can be chosen depending on the highest bid. - defaultWidth: 300, - defaultHeight: 600 - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 0ffb16d23a4..d99013c61f3 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -2,7 +2,6 @@ import Adapter from '../../src/adapter.js'; import {createBid} from '../../src/bidfactory.js'; import { bind, - cleanObj, createTrackPixelHtml, deepAccess, deepClone, @@ -24,21 +23,22 @@ import { logWarn, mergeDeep, parseSizesInput, - pick, timestamp, + timestamp, triggerPixel, uniques } from '../../src/utils.js'; import CONSTANTS from '../../src/constants.json'; import adapterManager from '../../src/adapterManager.js'; -import { config } from '../../src/config.js'; -import { VIDEO, NATIVE } from '../../src/mediaTypes.js'; -import { isValid } from '../../src/adapters/bidderFactory.js'; +import {config} from '../../src/config.js'; +import {NATIVE, VIDEO} from '../../src/mediaTypes.js'; +import {isValid} from '../../src/adapters/bidderFactory.js'; import * as events from '../../src/events.js'; import {find, includes} from '../../src/polyfill.js'; -import { S2S_VENDORS } from './config.js'; -import { ajax } from '../../src/ajax.js'; +import {S2S_VENDORS} from './config.js'; +import {ajax} from '../../src/ajax.js'; import {hook} from '../../src/hook.js'; import {getGlobal} from '../../src/prebidGlobal.js'; +import {hasPurpose1Consent} from '../../src/utils/gpdr.js'; const getConfig = config.getConfig; @@ -360,7 +360,7 @@ function _appendSiteAppDevice(request, pageUrl, accountId) { // ORTB specifies app OR site if (typeof config.getConfig('app') === 'object') { request.app = config.getConfig('app'); - request.app.publisher = {id: accountId} + request.app.publisher = {id: accountId}; } else { request.site = {}; if (isPlainObject(config.getConfig('site'))) { @@ -389,18 +389,13 @@ function _appendSiteAppDevice(request, pageUrl, accountId) { } } -function addBidderFirstPartyDataToRequest(request) { - const bidderConfig = config.getBidderConfig(); - const fpdConfigs = Object.keys(bidderConfig).reduce((acc, bidder) => { - const currBidderConfig = bidderConfig[bidder]; - if (currBidderConfig.ortb2) { - const ortb2 = mergeDeep({}, currBidderConfig.ortb2); - - acc.push({ - bidders: [ bidder ], - config: { ortb2 } - }); - } +function addBidderFirstPartyDataToRequest(request, bidderFpd) { + const fpdConfigs = Object.entries(bidderFpd).reduce((acc, [bidder, bidderOrtb2]) => { + const ortb2 = mergeDeep({}, bidderOrtb2); + acc.push({ + bidders: [ bidder ], + config: { ortb2 } + }); return acc; }, []); @@ -410,20 +405,14 @@ function addBidderFirstPartyDataToRequest(request) { } // https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40 -let nativeDataIdMap = { - sponsoredBy: 1, // sponsored - body: 2, // desc - rating: 3, - likes: 4, - downloads: 5, - price: 6, - salePrice: 7, - phone: 8, - address: 9, - body2: 10, // desc2 - cta: 12 // ctatext -}; -let nativeDataNames = Object.keys(nativeDataIdMap); +let nativeDataNames = Object.keys(CONSTANTS.PREBID_NATIVE_DATA_KEYS_TO_ORTB); + +// returns object with legacy asset name as key and asset id as value: +// { "sponsoredBy": 1, ... } +let nativeDataIdMap = nativeDataNames.reduce((prev, key) => { + prev[key] = CONSTANTS.NATIVE_ASSET_TYPES[CONSTANTS.PREBID_NATIVE_DATA_KEYS_TO_ORTB[key]]; + return prev; +}, {}); let nativeImgIdMap = { icon: 1, @@ -442,23 +431,19 @@ let nativeEventTrackerMethodMap = { js: 2 }; -// enable reverse lookup -[ - nativeDataIdMap, - nativeImgIdMap, - nativeEventTrackerEventMap, - nativeEventTrackerMethodMap -].forEach(map => { - Object.keys(map).forEach(key => { - map[map[key]] = key; +if (FEATURES.NATIVE) { + // enable reverse lookup + [ + nativeDataIdMap, + nativeImgIdMap, + nativeEventTrackerEventMap, + nativeEventTrackerMethodMap + ].forEach(map => { + Object.keys(map).forEach(key => { + map[map[key]] = key; + }); }); -}); - -/* - * Protocol spec for OpenRTB endpoint - * e.g., https:///v1/openrtb2/auction - */ -let nativeAssetCache = {}; // store processed native params to preserve +} /** * map wurl to auction id and adId for use in the BID_WON event @@ -546,7 +531,7 @@ Object.assign(ORTB2.prototype, { if (bid.mediaTypes != null) { logWarn(`Prebid Server adapter does not (yet) support bidder-specific mediaTypes for the same adUnit. Size mapping configuration will be ignored for adUnit: ${adUnit.code}, bidder: ${bid.bidder}`); } - }) + }); // in case there is a duplicate imp.id, add '-2' suffix to the second imp.id. // e.g. if there are 2 adUnits (case of twin adUnit codes) with code 'test', @@ -560,78 +545,6 @@ Object.assign(ORTB2.prototype, { impIds.add(impressionId); this.adUnitsByImp[impressionId] = adUnit; - const nativeParams = adUnit.nativeParams; - let nativeAssets; - if (nativeParams) { - let idCounter = -1; - try { - nativeAssets = nativeAssetCache[impressionId] = Object.keys(nativeParams).reduce((assets, type) => { - let params = nativeParams[type]; - - function newAsset(obj) { - idCounter++; - return Object.assign({ - required: params.required ? 1 : 0, - id: (isNumber(params.id)) ? idCounter = params.id : idCounter - }, obj ? cleanObj(obj) : {}); - } - - switch (type) { - case 'image': - case 'icon': - let imgTypeId = nativeImgIdMap[type]; - let asset = cleanObj({ - type: imgTypeId, - w: deepAccess(params, 'sizes.0'), - h: deepAccess(params, 'sizes.1'), - wmin: deepAccess(params, 'aspect_ratios.0.min_width'), - hmin: deepAccess(params, 'aspect_ratios.0.min_height') - }); - if (!((asset.w && asset.h) || (asset.hmin && asset.wmin))) { - throw 'invalid img sizes (must provide sizes or min_height & min_width if using aspect_ratios)'; - } - if (Array.isArray(params.aspect_ratios)) { - // pass aspect_ratios as ext data I guess? - const aspectRatios = params.aspect_ratios - .filter((ar) => ar.ratio_width && ar.ratio_height) - .map(ratio => `${ratio.ratio_width}:${ratio.ratio_height}`); - if (aspectRatios.length > 0) { - asset.ext = { - aspectratios: aspectRatios - } - } - } - assets.push(newAsset({ - img: asset - })); - break; - case 'title': - if (!params.len) { - throw 'invalid title.len'; - } - assets.push(newAsset({ - title: { - len: params.len - } - })); - break; - default: - let dataAssetTypeId = nativeDataIdMap[type]; - if (dataAssetTypeId) { - assets.push(newAsset({ - data: { - type: dataAssetTypeId, - len: params.len - } - })) - } - } - return assets; - }, []); - } catch (e) { - logError('error creating native request: ' + String(e)) - } - } const videoParams = deepAccess(adUnit, 'mediaTypes.video'); const bannerParams = deepAccess(adUnit, 'mediaTypes.banner'); @@ -686,25 +599,26 @@ Object.assign(ORTB2.prototype, { }, {}); } } - - if (nativeAssets) { + const nativeReq = deepAccess(adUnit, 'nativeOrtbRequest'); + if (FEATURES.NATIVE && nativeReq) { + const defaultRequest = { + // TODO: determine best way to pass these and if we allow defaults + context: 1, + plcmttype: 1, + eventtrackers: [ + { event: 1, methods: [1] } + ], + // TODO: figure out how to support privacy field + // privacy: int + }; try { - mediaTypes['native'] = { - request: JSON.stringify({ - // TODO: determine best way to pass these and if we allow defaults - context: 1, - plcmttype: 1, - eventtrackers: [ - {event: 1, methods: [1]} - ], - // TODO: figure out how to support privacy field - // privacy: int - assets: nativeAssets - }), + const request = Object.assign(defaultRequest, nativeReq); + mediaTypes[NATIVE] = { + request: JSON.stringify(request), ver: '1.2' - } + }; } catch (e) { - logError('error creating native request: ' + String(e)) + logError('error creating native request: ' + String(e)); } } @@ -716,7 +630,10 @@ Object.assign(ORTB2.prototype, { if (adapter && adapter.getSpec().transformBidParams) { bid.params = adapter.getSpec().transformBidParams(bid.params, true, adUnit, bidRequests); } - acc[bid.bidder] = (s2sConfig.adapterOptions && s2sConfig.adapterOptions[bid.bidder]) ? Object.assign({}, bid.params, s2sConfig.adapterOptions[bid.bidder]) : bid.params; + deepSetValue(acc, + `prebid.bidder.${bid.bidder}`, + (s2sConfig.adapterOptions && s2sConfig.adapterOptions[bid.bidder]) ? Object.assign({}, bid.params, s2sConfig.adapterOptions[bid.bidder]) : bid.params + ); return acc; }, {...deepAccess(adUnit, 'ortb2Imp.ext')}); @@ -753,12 +670,6 @@ Object.assign(ORTB2.prototype, { mergeDeep(imp, mediaTypes); - // if storedAuctionResponse has been set, pass SRID - const storedAuctionResponseBid = find(firstBidRequest.bids, bid => (bid.adUnitCode === adUnit.code && bid.storedAuctionResponse)); - if (storedAuctionResponseBid) { - deepSetValue(imp, 'ext.prebid.storedauctionresponse.id', storedAuctionResponseBid.storedAuctionResponse.toString()); - } - const floor = (() => { // we have to pick a floor for the imp - here we attempt to find the minimum floor // across all bids for this adUnit @@ -816,7 +727,7 @@ Object.assign(ORTB2.prototype, { if (floor) { imp.bidfloor = floor.floor; - imp.bidfloorcur = floor.currency + imp.bidfloorcur = floor.currency; } if (imp.banner || imp.video || imp.native) { @@ -855,11 +766,11 @@ Object.assign(ORTB2.prototype, { } // This is no longer overwritten unless name and version explicitly overwritten by extPrebid (mergeDeep) - request.ext.prebid = Object.assign(request.ext.prebid, {channel: {name: 'pbjs', version: $$PREBID_GLOBAL$$.version}}) + request.ext.prebid = Object.assign(request.ext.prebid, {channel: {name: 'pbjs', version: $$PREBID_GLOBAL$$.version}}); // set debug flag if in debug mode if (getConfig('debug')) { - request.ext.prebid = Object.assign(request.ext.prebid, {debug: true}) + request.ext.prebid = Object.assign(request.ext.prebid, {debug: true}); } // s2sConfig video.ext.prebid is passed through openrtb to PBS @@ -867,6 +778,43 @@ Object.assign(ORTB2.prototype, { request.ext.prebid = mergeDeep(request.ext.prebid, s2sConfig.extPrebid); } + // get reference to pbs config schain bidder names (if any exist) + const pbsSchainBidderNamesArr = request.ext.prebid?.schains ? request.ext.prebid.schains.flatMap(s => s.bidders) : []; + // create an schains object + const schains = Object.fromEntries( + (request.ext.prebid?.schains || []).map(({bidders, schain}) => [JSON.stringify(schain), {bidders: new Set(bidders), schain}]) + ); + + // compare bidder specific schains with pbs specific schains + request.ext.prebid.schains = Object.values( + bidRequests + .map((req) => [req.bidderCode, req.bids[0].schain]) + .reduce((chains, [bidder, chain]) => { + const chainKey = JSON.stringify(chain); + + switch (true) { + // if pbjs bidder name is same as pbs bidder name, pbs bidder name always wins + case chainKey && pbsSchainBidderNamesArr.indexOf(bidder) !== -1: + logInfo(`bidder-specific schain for ${bidder} skipped due to existing entry`); + break; + // if a pbjs schain obj is equal to an schain obj that exists on the pbs side, add the bidder name on the pbs side + case chainKey && chains.hasOwnProperty(chainKey) && pbsSchainBidderNamesArr.indexOf(bidder) === -1: + chains[chainKey].bidders.add(bidder); + break; + // if a pbjs schain obj is not on the pbs side, add a new schain entry on the pbs side + case chainKey && !chains.hasOwnProperty(chainKey): + chains[chainKey] = {bidders: new Set(), schain: chain}; + chains[chainKey].bidders.add(bidder); + break; + default: + } + + return chains; + }, schains) + ).map(({bidders, schain}) => ({bidders: Array.from(bidders), schain})); + // if schains evaluates to an empty array, remove it from the prebid object + if (request.ext.prebid.schains.length === 0) delete request.ext.prebid.schains; + /** * @type {(string[]|string|undefined)} - OpenRTB property 'cur', currencies available for bids */ @@ -879,7 +827,7 @@ Object.assign(ORTB2.prototype, { request.cur = [adServerCur[0]]; } - _appendSiteAppDevice(request, bidRequests[0].refererInfo.referer, s2sConfig.accountId); + _appendSiteAppDevice(request, bidRequests[0].refererInfo.page, s2sConfig.accountId); // pass schain object if it is present const schain = deepAccess(bidRequests, '0.bids.0.schain'); @@ -948,10 +896,10 @@ Object.assign(ORTB2.prototype, { deepSetValue(request, 'regs.coppa', 1); } - const commonFpd = getConfig('ortb2') || {}; + const commonFpd = s2sBidRequest.ortb2Fragments?.global || {}; mergeDeep(request, commonFpd); - addBidderFirstPartyDataToRequest(request); + addBidderFirstPartyDataToRequest(request, s2sBidRequest.ortb2Fragments?.bidder || {}); request.imp.forEach((imp) => this.impRequested[imp.id] = imp); return request; @@ -990,6 +938,13 @@ Object.assign(ORTB2.prototype, { }); bidObject.requestTimestamp = this.requestTimestamp; bidObject.cpm = cpm; + if (bid?.ext?.prebid?.meta?.adaptercode) { + bidObject.adapterCode = bid.ext.prebid.meta.adaptercode; + } else if (bidRequest?.bidder) { + bidObject.adapterCode = bidRequest.bidder; + } else { + bidObject.adapterCode = seatbid.seat; + } // temporarily leaving attaching it to each bidResponse so no breaking change // BUT: this is a flat map, so it should be only attached to bidderRequest, a the change above does @@ -1043,57 +998,19 @@ Object.assign(ORTB2.prototype, { if (bid.adm) { bidObject.vastXml = bid.adm; } if (!bidObject.vastUrl && bid.nurl) { bidObject.vastUrl = bid.nurl; } - } else if (deepAccess(bid, 'ext.prebid.type') === NATIVE) { + } else if (FEATURES.NATIVE && deepAccess(bid, 'ext.prebid.type') === NATIVE) { bidObject.mediaType = NATIVE; - let adm; + let ortb; if (typeof bid.adm === 'string') { - adm = bidObject.adm = JSON.parse(bid.adm); + ortb = bidObject.adm = JSON.parse(bid.adm); } else { - adm = bidObject.adm = bid.adm; + ortb = bidObject.adm = bid.adm; } - let trackers = { - [nativeEventTrackerMethodMap.img]: adm.imptrackers || [], - [nativeEventTrackerMethodMap.js]: adm.jstracker ? [adm.jstracker] : [] - }; - if (adm.eventtrackers) { - adm.eventtrackers.forEach(tracker => { - switch (tracker.method) { - case nativeEventTrackerMethodMap.img: - trackers[nativeEventTrackerMethodMap.img].push(tracker.url); - break; - case nativeEventTrackerMethodMap.js: - trackers[nativeEventTrackerMethodMap.js].push(tracker.url); - break; - } - }); - } - - if (isPlainObject(adm) && Array.isArray(adm.assets)) { - let origAssets = nativeAssetCache[bid.impid]; - bidObject.native = cleanObj(adm.assets.reduce((native, asset) => { - let origAsset = origAssets[asset.id]; - if (isPlainObject(asset.img)) { - native[origAsset.img.type ? nativeImgIdMap[origAsset.img.type] : 'image'] = pick( - asset.img, - ['url', 'w as width', 'h as height'] - ); - } else if (isPlainObject(asset.title)) { - native['title'] = asset.title.text - } else if (isPlainObject(asset.data)) { - nativeDataNames.forEach(dataType => { - if (nativeDataIdMap[dataType] === origAsset.data.type) { - native[dataType] = asset.data.value; - } - }); - } - return native; - }, cleanObj({ - clickUrl: adm.link, - clickTrackers: deepAccess(adm, 'link.clicktrackers'), - impressionTrackers: trackers[nativeEventTrackerMethodMap.img], - javascriptTrackers: trackers[nativeEventTrackerMethodMap.js] - }))); + if (isPlainObject(ortb) && Array.isArray(ortb.assets)) { + bidObject.native = { + ortb, + }; } else { logError('prebid server native response contained no assets'); } @@ -1159,16 +1076,6 @@ function bidWonHandler(bid) { } } -function hasPurpose1Consent(gdprConsent) { - let result = true; - if (gdprConsent) { - if (gdprConsent.gdprApplies && gdprConsent.apiVersion === 2) { - result = !!(deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true); - } - } - return result; -} - function getMatchingConsentUrl(urlProp, gdprConsent) { return hasPurpose1Consent(gdprConsent) ? urlProp.p1Consent : urlProp.noP1Consent; } diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index 6235b10fa13..1180a74db30 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { generateUUID, getParameterByName, logError, parseUrl, logInfo } from '../src/utils.js'; import {ajaxBuilder} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; import CONSTANTS from '../src/constants.json'; @@ -9,7 +9,7 @@ import CONSTANTS from '../src/constants.json'; * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager */ export const storage = getStorageManager({gvlid: undefined, moduleName: 'prebidmanager'}); -const DEFAULT_EVENT_URL = 'https://endpoint.prebidmanager.com/endpoint' +const DEFAULT_EVENT_URL = 'https://endpoint.prebidmanager.com/endpoint'; const analyticsType = 'endpoint'; const analyticsName = 'Prebid Manager Analytics: '; diff --git a/modules/priceFloors.js b/modules/priceFloors.js index e548de768b4..c972e024780 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -75,11 +75,15 @@ function roundUp(number, precision) { return Math.ceil((parseFloat(number) * Math.pow(10, precision)).toFixed(1)) / Math.pow(10, precision); } -let referrerHostname; -function getHostNameFromReferer(referer) { - referrerHostname = parseUrl(referer, {noDecodeWholeURL: true}).hostname; - return referrerHostname; -} +const getHostname = (() => { + let domain; + return function() { + if (domain == null) { + domain = parseUrl(getRefererInfo().topmostLocation, {noDecodeWholeUrl: true}).hostname; + } + return domain; + } +})(); // First look into bidRequest! function getGptSlotFromAdUnit(transactionId, {index = auctionManager.index} = {}) { @@ -99,7 +103,7 @@ export let fieldMatchingFunctions = { 'size': (bidRequest, bidResponse) => parseGPTSingleSizeArray(bidResponse.size) || '*', 'mediaType': (bidRequest, bidResponse) => bidResponse.mediaType || 'banner', 'gptSlot': (bidRequest, bidResponse) => getGptSlotFromAdUnit((bidRequest || bidResponse).transactionId) || getGptSlotInfoForAdUnitCode(getAdUnitCode(bidRequest, bidResponse)).gptSlot, - 'domain': (bidRequest, bidResponse) => referrerHostname || getHostNameFromReferer(getRefererInfo().referer), + 'domain': getHostname, 'adUnitCode': (bidRequest, bidResponse) => getAdUnitCode(bidRequest, bidResponse) } @@ -696,7 +700,7 @@ export function addBidResponseHook(fn, adUnitCode, bid) { return fn.call(this, adUnitCode, bid); } - const matchingBidRequest = auctionManager.index.getBidRequest(bid) + const matchingBidRequest = auctionManager.index.getBidRequest(bid); // get the matching rule let floorInfo = getFirstMatchingFloor(floorData.data, matchingBidRequest, {...bid, size: [bid.width, bid.height]}); @@ -747,7 +751,7 @@ export function addBidResponseHook(fn, adUnitCode, bid) { flooredBid.status = CONSTANTS.BID_STATUS.BID_REJECTED; // if floor not met update bid with 0 cpm so it is not included downstream and marked as no-bid flooredBid.cpm = 0; - logWarn(`${MODULE_NAME}: ${flooredBid.bidderCode}'s Bid Response for ${adUnitCode} was rejected due to floor not met`, bid); + logWarn(`${MODULE_NAME}: ${flooredBid.bidderCode}'s Bid Response for ${adUnitCode} was rejected due to floor not met (adjusted cpm: ${bid?.floorData?.cpmAfterAdjustments}, floor: ${floorInfo?.matchingFloor})`, bid); return fn.call(this, adUnitCode, flooredBid); } return fn.call(this, adUnitCode, bid); diff --git a/modules/prismaBidAdapter.js b/modules/prismaBidAdapter.js new file mode 100644 index 00000000000..52a8c18a6fd --- /dev/null +++ b/modules/prismaBidAdapter.js @@ -0,0 +1,199 @@ +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import { transformBidderParamKeywords } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'prisma'; +const BIDDER_URL = 'https://prisma.nexx360.io/prebid'; +const CACHE_URL = 'https://prisma.nexx360.io/cache'; +const METRICS_TRACKER_URL = 'https://prisma.nexx360.io/track-imp'; + +const GVLID = 965; + +function getConnectionType() { + const connection = navigator.connection || navigator.webkitConnection; + if (!connection) { + return 0; + } + switch (connection.type) { + case 'ethernet': + return 1; + case 'wifi': + return 2; + case 'cellular': + switch (connection.effectiveType) { + case 'slow-2g': + case '2g': + return 4; + case '3g': + return 5; + case '4g': + return 6; + default: + return 3; + } + default: + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: ['prismadirect'], // short code + supportedMediaTypes: [BANNER, VIDEO], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.account && bid.params.tagId); + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const adUnits = []; + const test = config.getConfig('debug') ? 1 : 0; + let adunitValue = null; + let userEids = null; + Object.keys(validBidRequests).forEach(key => { + adunitValue = validBidRequests[key]; + const foo = { + account: adunitValue.params.account, + tagId: adunitValue.params.tagId, + label: adunitValue.adUnitCode, + bidId: adunitValue.bidId, + auctionId: adunitValue.auctionId, + transactionId: adunitValue.transactionId, + mediatypes: adunitValue.mediaTypes, + bidfloor: 0, + bidfloorCurrency: 'USD', + keywords: adunitValue.params.keywords ? transformBidderParamKeywords(adunitValue.params.keywords) : [], + } + adUnits.push(foo); + if (adunitValue.userIdAsEids) userEids = adunitValue.userIdAsEids; + }); + const payload = { + adUnits, + // TODO: does the fallback make sense here? + href: encodeURIComponent(bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation) + }; + if (bidderRequest) { // modules informations (gdpr, ccpa, schain, userId) + if (bidderRequest.gdprConsent) { + payload.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + payload.gdprConsent = bidderRequest.gdprConsent.consentString; + } else { + payload.gdpr = 0; + payload.gdprConsent = ''; + } + if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } + if (bidderRequest.schain) { payload.schain = bidderRequest.schain; } + if (userEids !== null) payload.userEids = userEids; + }; + payload.connectionType = getConnectionType(); + + if (test) payload.test = 1; + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: BIDDER_URL, + data: payloadString, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + const serverBody = serverResponse.body; + const bidResponses = []; + let bidResponse = null; + let value = null; + if (serverBody.hasOwnProperty('responses')) { + Object.keys(serverBody['responses']).forEach(key => { + value = serverBody['responses'][key]; + const url = `${CACHE_URL}?uuid=${value['uuid']}`; + bidResponse = { + requestId: value['bidId'], + cpm: value['cpm'], + currency: value['currency'], + width: value['width'], + height: value['height'], + ttl: value['ttl'], + creativeId: value['creativeId'], + netRevenue: true, + prisma: { + 'ssp': value['bidder'], + 'consent': value['consent'], + 'tagId': value['tagId'] + }, + meta: { + 'advertiserDomains': value['adomain'] || [] + } + }; + if (value.type === 'banner') bidResponse.adUrl = url; + if (value.type === 'video') { + const params = { + type: 'prebid', + mediatype: 'video', + ssp: value.bidder, + tag_id: value.tagId, + consent: value.consent, + price: value.cpm, + }; + bidResponse.cpm = value.cpm; + bidResponse.mediaType = 'video'; + bidResponse.vastUrl = url; + bidResponse.vastImpUrl = `${METRICS_TRACKER_URL}?${new URLSearchParams(params).toString()}`; + } + bidResponses.push(bidResponse); + }); + } + return bidResponses; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') && + serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { + return serverResponses[0].body.cookies.slice(0, 5); + } else { + return []; + } + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + onBidWon: function(bid) { + // fires a pixel to confirm a winning bid + const params = { type: 'prebid', mediatype: 'banner' }; + if (bid.hasOwnProperty('prisma')) { + if (bid.prisma.hasOwnProperty('ssp')) params.ssp = bid.prisma.ssp; + if (bid.prisma.hasOwnProperty('tagId')) params.tag_id = bid.prisma.tagId; + if (bid.prisma.hasOwnProperty('consent')) params.consent = bid.prisma.consent; + }; + params.price = bid.cpm; + const url = `${METRICS_TRACKER_URL}?${new URLSearchParams(params).toString()}`; + ajax(url, null, undefined, {method: 'GET', withCredentials: true}); + return true; + } + +} +registerBidder(spec); diff --git a/modules/prismaBidAdapter.md b/modules/prismaBidAdapter.md new file mode 100644 index 00000000000..a400183cec6 --- /dev/null +++ b/modules/prismaBidAdapter.md @@ -0,0 +1,59 @@ +# Overview + +``` +Module Name: Prisma Bid Adapter +Module Type: Bidder Adapter +Maintainer: gabriel@nexx360.io +``` + +# Description + +Connects to Prisma network for bids. + +To use us as a bidder you must have an account and an active "tagId" on our platform. + +# Test Parameters + +## Web + +### Display +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'prisma', + params: { + account: '1067', + tagId: 'luvxjvgn' + } + }] + }, +]; +``` + +### Video Instream +``` + var videoAdUnit = { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'prisma', + params: { + account: '1067', + tagId: 'luvxjvgn' + } + }] + }; +``` diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index 42a98bcdb09..04d363be7fb 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -54,10 +54,8 @@ function _createServerRequest(bidRequests, bidderRequest) { if (gdprConsent.vendorData) { var vendorData = gdprConsent.vendorData; - var apiVersion = gdprConsent.apiVersion; if ( - apiVersion === 2 && vendorData.vendor && vendorData.vendor.consents && typeof vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)] !== @@ -65,14 +63,6 @@ function _createServerRequest(bidRequests, bidderRequest) { ) { payload.gdpr.consentGiven = !!vendorData.vendor.consents[PROXISTORE_VENDOR_ID.toString(10)]; - } else if ( - apiVersion === 1 && - vendorData.vendorConsents && - typeof vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)] !== - 'undefined' - ) { - payload.gdpr.consentGiven = - !!vendorData.vendorConsents[PROXISTORE_VENDOR_ID.toString(10)]; } } } diff --git a/modules/pubCommonId.js b/modules/pubCommonId.js index e88cf7b9e7d..e3fd21e7260 100644 --- a/modules/pubCommonId.js +++ b/modules/pubCommonId.js @@ -9,8 +9,7 @@ import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; -const MODULE_TYPE = 'fpid-module'; -const storage = getStorageManager({moduleType: MODULE_TYPE}); +const storage = getStorageManager({moduleName: 'pubCommonId'}); const ID_NAME = '_pubcid'; const OPTOUT_NAME = '_pubcid_optout'; diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index 28c4fdefd42..5e7ce6fc9fb 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -228,20 +228,15 @@ function buildSite(bidderRequest) { let site = null; const { refererInfo } = bidderRequest; - const pageUrl = config.getConfig('pageUrl') || refererInfo.canonicalUrl || refererInfo.referer; + const pageUrl = refererInfo.page; if (pageUrl) { site = site || {}; site.page = pageUrl; } - if (refererInfo.reachedTop) { - try { - const pageRef = window.top.document.referrer; - if (pageRef) { - site = site || {}; - site.ref = pageRef; - } - } catch (e) {} + if (refererInfo.ref) { + site = site || {}; + site.ref = refererInfo.ref; } return site; diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 16e54fbdcc8..c8dc7cef15d 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -1,5 +1,5 @@ import { _each, pick, logWarn, isStr, isArray, logError } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; @@ -358,7 +358,7 @@ function auctionInitHandler(args) { 'bidderDonePendingCount', () => args.bidderRequests.length, ]); cacheEntry.adUnitCodes = {}; - cacheEntry.referer = args.bidderRequests[0].refererInfo.referer; + cacheEntry.referer = args.bidderRequests[0].refererInfo.topmostLocation; cache.auctions[args.auctionId] = cacheEntry; } @@ -382,9 +382,9 @@ function bidResponseHandler(args) { return; } - if (bid.bidder && args.bidderCode && bid.bidder !== args.bidderCode) { + if ((bid.bidder && args.bidderCode && bid.bidder !== args.bidderCode) || (bid.bidder === args.bidderCode && bid.status === SUCCESS)) { bid = copyRequiredBidDetails(args); - cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId].push(bid) + cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId].push(bid); } bid.adId = args.adId; bid.source = formatSource(bid.source || args.source); diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index b18e1b73604..7f346b1c7b0 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,9 +1,10 @@ -import { logWarn, _each, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes } from '../src/utils.js'; +import { logWarn, _each, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes, uniques } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { bidderSettings } from '../src/bidderSettings.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; @@ -111,10 +112,6 @@ const dealChannelValues = { 6: 'PMPG' }; -const FLOC_FORMAT = { - 'EID': 1, - 'SEGMENT': 2 -} // BB stands for Blue BillyWig const BB_RENDERER = { bootstrapPlayer: function(bid) { @@ -180,13 +177,15 @@ let publisherId = 0; let isInvalidNativeRequest = false; let NATIVE_ASSET_ID_TO_KEY_MAP = {}; let NATIVE_ASSET_KEY_TO_ASSET_MAP = {}; +let biddersList = ['pubmatic']; +const allBiddersList = ['all']; // loading NATIVE_ASSET_ID_TO_KEY_MAP _each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAsset.KEY }); // loading NATIVE_ASSET_KEY_TO_ASSET_MAP _each(NATIVE_ASSETS, anAsset => { NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); -function _getDomainFromURL(url) { +export function _getDomainFromURL(url) { let anchor = document.createElement('a'); anchor.href = url; return anchor.hostname; @@ -273,8 +272,9 @@ function _parseAdSlot(bid) { function _initConf(refererInfo) { return { - pageURL: (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href, - refURL: window.document.referrer + // TODO: do the fallbacks make sense here? + pageURL: refererInfo?.page || window.location.href, + refURL: refererInfo?.ref || window.document.referrer }; } @@ -608,7 +608,7 @@ function _addDealCustomTargetings(imp, bid) { if (dctr.substring(dctrLen, dctrLen - 1) === '|') { dctr = dctr.substring(0, dctrLen - 1); } - imp.ext['key_val'] = dctr.trim() + imp.ext['key_val'] = dctr.trim(); } else { logWarn(LOG_WARN_PREFIX + 'Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value'); } @@ -799,67 +799,8 @@ function _addFloorFromFloorModule(impObj, bid) { logInfo(LOG_WARN_PREFIX, 'new impObj.bidfloor value:', impObj.bidfloor); } -function _getFlocId(validBidRequests, flocFormat) { - var flocIdObject = null; - var flocId = deepAccess(validBidRequests, '0.userId.flocId'); - if (flocId && flocId.id) { - switch (flocFormat) { - case FLOC_FORMAT.SEGMENT: - flocIdObject = { - id: 'FLOC', - name: 'FLOC', - ext: { - ver: flocId.version - }, - segment: [{ - id: flocId.id, - name: 'chrome.com', - value: flocId.id.toString() - }] - } - break; - case FLOC_FORMAT.EID: - default: - flocIdObject = { - source: 'chrome.com', - uids: [ - { - atype: 1, - id: flocId.id, - ext: { - ver: flocId.version - } - }, - ] - } - break; - } - } - return flocIdObject; -} - -function _handleFlocId(payload, validBidRequests) { - var flocObject = _getFlocId(validBidRequests, FLOC_FORMAT.SEGMENT); - if (flocObject) { - if (!payload.user) { - payload.user = {}; - } - if (!payload.user.data) { - payload.user.data = []; - } - payload.user.data.push(flocObject); - } -} - function _handleEids(payload, validBidRequests) { let bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids'); - let flocObject = _getFlocId(validBidRequests, FLOC_FORMAT.EID); - if (flocObject) { - if (!bidUserIdAsEids) { - bidUserIdAsEids = []; - } - bidUserIdAsEids.push(flocObject); - } if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { deepSetValue(payload, 'user.eids', bidUserIdAsEids); } @@ -870,7 +811,7 @@ function _checkMediaType(bid, newBid) { if (bid.ext && bid.ext['bidtype'] != undefined) { newBid.mediaType = MEDIATYPE[bid.ext.bidtype]; } else { - logInfo(LOG_WARN_PREFIX + 'bid.ext.bidtype does not exist, checking alternatively for mediaType') + logInfo(LOG_WARN_PREFIX + 'bid.ext.bidtype does not exist, checking alternatively for mediaType'); var adm = bid.adm; var admStr = ''; var videoRegex = new RegExp(/VAST\s+version/); @@ -1083,6 +1024,8 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -1148,10 +1091,21 @@ export const spec = { payload.ext.wrapper.wv = $$REPO_AND_VERSION$$; payload.ext.wrapper.transactionId = conf.transactionId; payload.ext.wrapper.wp = 'pbjs'; - if (bidderRequest && bidderRequest.bidderCode) { - payload.ext.allowAlternateBidderCodes = bidderSettings.get(bidderRequest.bidderCode, 'allowAlternateBidderCodes'); - payload.ext.allowedAlternateBidderCodes = bidderSettings.get(bidderRequest.bidderCode, 'allowedAlternateBidderCodes'); + const allowAlternateBidder = bidderRequest ? bidderSettings.get(bidderRequest.bidderCode, 'allowAlternateBidderCodes') : undefined; + if (allowAlternateBidder !== undefined) { + payload.ext.marketplace = {}; + if (bidderRequest && allowAlternateBidder == true) { + let allowedBiddersList = bidderSettings.get(bidderRequest.bidderCode, 'allowedAlternateBidderCodes'); + if (isArray(allowedBiddersList)) { + allowedBiddersList = allowedBiddersList.map(val => val.trim().toLowerCase()).filter(val => !!val).filter(uniques); + biddersList = allowedBiddersList.includes('*') ? allBiddersList : [...biddersList, ...allowedBiddersList]; + } else { + biddersList = allBiddersList; + } + } + payload.ext.marketplace.allowedbidders = biddersList.filter(uniques); } + payload.user.gender = (conf.gender ? conf.gender.trim() : UNDEFINED); payload.user.geo = {}; payload.user.geo.lat = _parseSlotParam('lat', conf.lat); @@ -1171,6 +1125,9 @@ export const spec = { payload.device = Object.assign(payload.device, config.getConfig('device')); } + // update device.language to ISO-639-1-alpha-2 (2 character language) + payload.device.language = payload.device.language && payload.device.language.split('-')[0]; + // passing transactionId in source.tid deepSetValue(payload, 'source.tid', conf.transactionId); @@ -1202,18 +1159,22 @@ export const spec = { _handleEids(payload, validBidRequests); - _handleFlocId(payload, validBidRequests); // First Party Data - const commonFpd = config.getConfig('ortb2') || {}; + const commonFpd = (bidderRequest && bidderRequest.ortb2) || {}; if (commonFpd.site) { + const { page, domain, ref } = payload.site; mergeDeep(payload, {site: commonFpd.site}); + payload.site.page = page; + payload.site.domain = domain; + payload.site.ref = ref; } if (commonFpd.user) { mergeDeep(payload, {user: commonFpd.user}); } if (commonFpd.bcat) { - blockedIabCategories = blockedIabCategories.concat(commonFpd.bcat) + blockedIabCategories = blockedIabCategories.concat(commonFpd.bcat); } + if (commonFpd.ext?.prebid?.bidderparams?.[bidderRequest.bidderCode]?.acat) { const acatParams = commonFpd.ext.prebid.bidderparams[bidderRequest.bidderCode].acat; _allowedIabCategoriesValidation(payload, acatParams); @@ -1221,6 +1182,19 @@ export const spec = { _allowedIabCategoriesValidation(payload, allowedIabCategories); } _blockedIabCategoriesValidation(payload, blockedIabCategories); + + // Check if bidderRequest has timeout property if present send timeout as tmax value to translator request + // bidderRequest has timeout property if publisher sets during calling requestBids function from page + // if not bidderRequest contains global value set by Prebid + if (bidderRequest?.timeout) { + payload.tmax = bidderRequest.timeout || config.getConfig('bidderTimeout'); + } else { + payload.tmax = window?.PWT?.versionDetails?.timeout; + } + + // Sending epoch timestamp in request.ext object + payload.ext.epoch = new Date().getTime(); + // Note: Do not move this block up // if site object is set in Prebid config then we need to copy required fields from site into app and unset the site object if (typeof config.getConfig('app') === 'object') { diff --git a/modules/pubperfAnalyticsAdapter.js b/modules/pubperfAnalyticsAdapter.js index 9282d5814c0..9ef95adb77a 100644 --- a/modules/pubperfAnalyticsAdapter.js +++ b/modules/pubperfAnalyticsAdapter.js @@ -2,7 +2,7 @@ * Analytics Adapter for Pubperf */ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { logError } from '../src/utils.js'; diff --git a/modules/pubstackAnalyticsAdapter.js b/modules/pubstackAnalyticsAdapter.js index b1da40c5b89..ef33b2d75ae 100644 --- a/modules/pubstackAnalyticsAdapter.js +++ b/modules/pubstackAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; const pubstackAnalytics = adapter({ diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index 006d6da7eb7..19a74c7c245 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { getParameterByName, logInfo, generateUUID, debugTurnedOn } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; @@ -157,7 +157,7 @@ function extendUserSessionTimeout() { } function userSessionID() { - return storage.getDataFromLocalStorage(localStorageSessName()) ? localStorage.getItem(localStorageSessName()) : ''; + return storage.getDataFromLocalStorage(localStorageSessName()) || ''; } function sessionExpired() { @@ -224,7 +224,8 @@ function filterAuctionInit(data) { modified.refererInfo = {}; // handle clean referrer, we only need one if (typeof modified.bidderRequests !== 'undefined' && typeof modified.bidderRequests[0] !== 'undefined' && typeof modified.bidderRequests[0].refererInfo !== 'undefined') { - modified.refererInfo = modified.bidderRequests[0].refererInfo; + // TODO: please do not send internal data structures over the network + modified.refererInfo = modified.bidderRequests[0].refererInfo.legacy; } if (typeof modified.adUnitCodes !== 'undefined') { @@ -294,7 +295,7 @@ pubwiseAnalytics.handleEvent = function(eventType, data) { if (eventType === CONSTANTS.EVENTS.AUCTION_END || eventType === CONSTANTS.EVENTS.BID_WON) { flushEvents(); } -} +}; pubwiseAnalytics.storeSessionID = function (userSessID) { storage.setDataInLocalStorage(localStorageSessName(), userSessID); diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index a1b9ffb56a0..7721fe10459 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -2,6 +2,7 @@ import { _each, isStr, deepClone, isArray, deepSetValue, inIframe, logMessage, l import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const VERSION = '0.1.0'; const GVLID = 842; const NET_REVENUE = true; @@ -116,6 +117,9 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -157,7 +161,7 @@ export const spec = { } if (bid.params.isTest) { - payload.test = Number(bid.params.isTest) // should be 1 or 0 + payload.test = Number(bid.params.isTest); // should be 1 or 0 } payload.site.publisher.id = bid.params.siteId.trim(); payload.user.gender = (conf.gender ? conf.gender.trim() : UNDEFINED); @@ -203,7 +207,7 @@ export const spec = { deepSetValue(payload, 'regs.coppa', 1); } - var options = {contentType: 'text/plain'} + var options = {contentType: 'text/plain'}; _logInfo('buildRequests payload', payload); _logInfo('buildRequests bidderRequest', bidderRequest); @@ -459,7 +463,7 @@ function _createImpressionObject(bid, conf) { } } } else { - _logWarn('MediaTypes are Required for all Adunit Configs', bid) + _logWarn('MediaTypes are Required for all Adunit Configs', bid); } _addFloorFromFloorModule(impObj, bid); @@ -528,8 +532,9 @@ function _cleanSlotName(slotName) { function _initConf(refererInfo) { return { - pageURL: (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href, - refURL: window.document.referrer + // TODO: do the fallbacks make sense here? + pageURL: refererInfo?.page || window.location.href, + refURL: refererInfo?.ref || window.document.referrer }; } diff --git a/modules/pubxBidAdapter.js b/modules/pubxBidAdapter.js index 18d2bb11404..17c4ba3848e 100644 --- a/modules/pubxBidAdapter.js +++ b/modules/pubxBidAdapter.js @@ -76,7 +76,7 @@ export const spec = { } else { kwString = kwContents; } - kwEnc = encodeURIComponent(kwString) + kwEnc = encodeURIComponent(kwString); } else { } if (titleContent) { if (titleContent.length > 30) { diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 669bd062206..f7e73dd5fdd 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { deepAccess, getGptSlotInfoForAdUnitCode, parseSizesInput, getWindowLocation, buildUrl } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; @@ -91,7 +91,12 @@ function mapBidResponse(bidResponse, status) { } else { Object.assign(bid, { bidId: bidResponse.requestId, - floorProvider: events.floorDetail ? events.floorDetail.floorProvider : null, + floorProvider: events.floorDetail?.floorProvider || null, + floorFetchStatus: events.floorDetail?.fetchStatus || null, + floorLocation: events.floorDetail?.location || null, + floorModelVersion: events.floorDetail?.modelVersion || null, + floorSkipRate: events.floorDetail?.skipRate || 0, + isFloorSkipped: events.floorDetail?.skipped || false, isWinningBid: true, placementId: bidResponse.params ? deepAccess(bidResponse, 'params.0.placementId') : null, renderedSize: bidResponse.size, @@ -135,7 +140,7 @@ export function getOS() { // add sampling rate pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { return (Math.floor((Math.random() * samplingRate + 1)) === parseInt(samplingRate)); -} +}; function send(data, status) { if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { @@ -153,7 +158,7 @@ function send(data, status) { data.initOptions.auctionId = data.auctionInit.auctionId; delete data.auctionInit; - data.pmcDetail = {} + data.pmcDetail = {}; Object.assign(data.pmcDetail, { bidDensity: storage ? storage.getItem('pbx:dpbid') : null, maxBid: storage ? storage.getItem('pbx:mxbid') : null, diff --git a/modules/pulsepointAnalyticsAdapter.js b/modules/pulsepointAnalyticsAdapter.js index 375a817f257..999ae3dd3de 100644 --- a/modules/pulsepointAnalyticsAdapter.js +++ b/modules/pulsepointAnalyticsAdapter.js @@ -2,7 +2,7 @@ * pulsepoint.js - Analytics Adapter for PulsePoint */ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; var pulsepointAdapter = adapter({ diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index c0280e944ae..52931be0078 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -1,7 +1,8 @@ +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; /* eslint dot-notation:0, quote-props:0 */ -import { convertTypes, deepAccess, isArray, logError, isFn } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; +import {convertTypes, deepAccess, isArray, isFn, logError} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {Renderer} from '../src/Renderer.js'; const NATIVE_DEFAULTS = { TITLE_LEN: 100, @@ -39,13 +40,16 @@ export const spec = { ), buildRequests: (bidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + const request = { id: bidRequests[0].bidderRequestId, imp: bidRequests.map(slot => impression(slot)), site: site(bidRequests, bidderRequest), app: app(bidRequests), device: device(), - bcat: bidRequests[0].params.bcat, + bcat: deepAccess(bidderRequest.ortb2Imp, 'bcat') || bidRequests[0].params.bcat, badv: bidRequests[0].params.badv, user: user(bidRequests[0], bidderRequest), regs: regs(bidderRequest), @@ -92,7 +96,7 @@ function bidResponseAvailable(request, response) { const idToImpMap = {}; const idToBidMap = {}; const idToSlotConfig = {}; - const bidResponse = response.body + const bidResponse = response.body; // extract the request bids and the response bids, keyed by impr-id const ortbRequest = request.data; ortbRequest.imp.forEach(imp => { @@ -330,8 +334,9 @@ function site(bidRequests, bidderRequest) { publisher: { id: pubId.toString(), }, - ref: referrer(), - page: bidderRequest && bidderRequest.refererInfo ? bidderRequest.refererInfo.referer : '', + // TODO: does the fallback make sense here? + ref: bidderRequest?.refererInfo?.ref || window.document.referrer, + page: bidderRequest?.refererInfo?.page || '' } } return null; @@ -356,17 +361,6 @@ function app(bidderRequest) { return null; } -/** - * Attempts to capture the referrer url. - */ -function referrer() { - try { - return window.top.document.referrer; - } catch (e) { - return document.referrer; - } -} - /** * Produces an OpenRTB Device object. */ diff --git a/modules/pxyzBidAdapter.js b/modules/pxyzBidAdapter.js index e144eb84a01..b40e0c79d6b 100644 --- a/modules/pxyzBidAdapter.js +++ b/modules/pxyzBidAdapter.js @@ -32,7 +32,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { - const referer = bidderRequest.refererInfo.referer; + const referer = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; const parts = referer.split('/'); let protocol, hostname; diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 449c7d12d6f..19a559edfda 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -4,6 +4,7 @@ import {config} from '../src/config.js'; import {getStorageManager} from '../src/storageManager.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {find} from '../src/polyfill.js'; +import {parseDomain} from '../src/refererDetection.js'; const BIDDER_CODE = 'quantcast'; const DEFAULT_BID_FLOOR = 0.0000000001; @@ -74,21 +75,7 @@ function makeBannerImp(bid) { }; } -function getDomain(url) { - if (!url) { - return url; - } - return url.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#]/)[0]; -} - -function checkTCFv1(vendorData) { - let vendorConsent = vendorData.vendorConsents && vendorData.vendorConsents[QUANTCAST_VENDOR_ID]; - let purposeConsent = vendorData.purposeConsents && vendorData.purposeConsents[PURPOSE_DATA_COLLECT]; - - return !!(vendorConsent && purposeConsent); -} - -function checkTCFv2(tcData) { +function checkTCF(tcData) { let restrictions = tcData.publisher ? tcData.publisher.restrictions : {}; let qcRestriction = restrictions && restrictions[PURPOSE_DATA_COLLECT] ? restrictions[PURPOSE_DATA_COLLECT][QUANTCAST_VENDOR_ID] @@ -144,19 +131,15 @@ export const spec = { const bids = bidRequests || []; const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') || {}; const uspConsent = deepAccess(bidderRequest, 'uspConsent'); - const referrer = deepAccess(bidderRequest, 'refererInfo.referer'); - const page = deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || deepAccess(window, 'location.href'); - const domain = getDomain(page); + const referrer = deepAccess(bidderRequest, 'refererInfo.ref'); + const page = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); + const domain = parseDomain(page, {noLeadingWww: true}); // Check for GDPR consent for purpose 1, and drop request if consent has not been given // Remaining consent checks are performed server-side. if (gdprConsent.gdprApplies) { if (gdprConsent.vendorData) { - if (gdprConsent.apiVersion === 1 && !checkTCFv1(gdprConsent.vendorData)) { - logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v1`); - return; - } - if (gdprConsent.apiVersion === 2 && !checkTCFv2(gdprConsent.vendorData)) { + if (!checkTCF(gdprConsent.vendorData)) { logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v2`); return; } diff --git a/modules/quantcastIdSystem.js b/modules/quantcastIdSystem.js index 7d82be884da..97cd01f98da 100644 --- a/modules/quantcastIdSystem.js +++ b/modules/quantcastIdSystem.js @@ -74,13 +74,7 @@ export function hasGDPRConsent(gdprConsent) { if (!gdprConsent.vendorData) { return false; } - if (gdprConsent.apiVersion === 1) { - // We are not supporting TCF v1 - return false; - } - if (gdprConsent.apiVersion === 2) { - return checkTCFv2(gdprConsent.vendorData); - } + return checkTCFv2(gdprConsent.vendorData); } return true; } @@ -217,7 +211,7 @@ export const quantcastIdSubmodule = { }); } - return { id: fpa ? { quantcastId: fpa } : undefined } + return { id: fpa ? { quantcastId: fpa } : undefined }; } }; diff --git a/modules/qwarryBidAdapter.js b/modules/qwarryBidAdapter.js index c9a86f73910..4b3e8fa8a19 100644 --- a/modules/qwarryBidAdapter.js +++ b/modules/qwarryBidAdapter.js @@ -28,7 +28,7 @@ export const spec = { let payload = { requestId: bidderRequest.bidderRequestId, bids, - referer: bidderRequest.refererInfo.referer, + referer: bidderRequest.refererInfo.page, schain: validBidRequests[0].schain } diff --git a/modules/radsBidAdapter.js b/modules/radsBidAdapter.js index fee5daa3fb4..fcb8c3a8c2a 100644 --- a/modules/radsBidAdapter.js +++ b/modules/radsBidAdapter.js @@ -23,7 +23,7 @@ export const spec = { const placementId = params.placement; const rnd = Math.floor(Math.random() * 99999999999); - const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + const referrer = encodeURIComponent(bidderRequest.refererInfo.page); const bidId = bidRequest.bidId; const isDev = params.devMode || false; @@ -65,7 +65,7 @@ export const spec = { method: 'GET', url: endpoint, data: objectToQueryString(payload), - } + }; }); }, interpretResponse: function(serverResponse, bidRequest) { @@ -184,7 +184,7 @@ function prepareExtraParams(params, payload, bidderRequest, bidRequest) { } if (params.bcat !== undefined) { - payload.bcat = params.bcat; + payload.bcat = deepAccess(bidderRequest.ortb2Imp, 'bcat') || params.bcat; } if (params.dvt !== undefined) { payload.dvt = params.dvt; diff --git a/modules/rakutenBidAdapter/index.js b/modules/rakutenBidAdapter/index.js index e567509b3c1..27c04029231 100644 --- a/modules/rakutenBidAdapter/index.js +++ b/modules/rakutenBidAdapter/index.js @@ -22,8 +22,9 @@ export const spec = { l: navigator.browserLanguage || navigator.language, d: document.domain, + // TODO: what are 'tp' and 'pp'? tp: bidderRequest.refererInfo.stack[0] || window.location.href, - pp: bidderRequest.refererInfo.referer, + pp: bidderRequest.refererInfo.topmostLocation, gdpr: ((_a = bidderRequest.gdprConsent) === null || _a === void 0 ? void 0 : _a.gdprApplies) ? 1 : 0, ...((_b = bidderRequest.gdprConsent) === null || _b === void 0 ? void 0 : _b.consentString) && { cd: bidderRequest.gdprConsent.consentString diff --git a/modules/rasBidAdapter.js b/modules/rasBidAdapter.js new file mode 100644 index 00000000000..7bc3cf66b0d --- /dev/null +++ b/modules/rasBidAdapter.js @@ -0,0 +1,159 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { isEmpty, getAdUnitSizes, parseSizesInput, deepAccess } from '../src/utils.js'; + +const BIDDER_CODE = 'ras'; +const VERSION = '1.0'; + +const getEndpoint = (network) => { + return `https://csr.onet.pl/${encodeURIComponent(network)}/csr-006/csr.json?nid=${encodeURIComponent(network)}&`; +}; + +function parseParams(params, bidderRequest) { + const newParams = {}; + const du = deepAccess(bidderRequest, 'refererInfo.page'); + const dr = deepAccess(bidderRequest, 'refererInfo.ref'); + + if (du) { + newParams.du = du; + } + if (dr) { + newParams.dr = dr; + } + const pageContext = params.pageContext; + if (!pageContext) { + return newParams; + } + if (pageContext.du) { + newParams.du = pageContext.du; + } + if (pageContext.dr) { + newParams.dr = pageContext.dr; + } + if (pageContext.dv) { + newParams.DV = pageContext.dv; + } + if (pageContext.keyWords && Array.isArray(pageContext.keyWords)) { + newParams.kwrd = pageContext.keyWords.join('+'); + } + if (pageContext.capping) { + newParams.local_capping = pageContext.capping; + } + if (pageContext.keyValues && typeof pageContext.keyValues === 'object') { + for (const param in pageContext.keyValues) { + if (pageContext.keyValues.hasOwnProperty(param)) { + const kvName = 'kv' + param; + newParams[kvName] = pageContext.keyValues[param]; + } + } + } + return newParams; +} + +const buildBid = (ad) => { + if (ad.type === 'empty') { + return null; + } + return { + requestId: ad.id, + cpm: ad.bid_rate ? ad.bid_rate.toFixed(2) : 0, + width: ad.width || 0, + height: ad.height || 0, + ttl: 300, + creativeId: ad.adid ? parseInt(ad.adid.split(',')[2], 10) : 0, + netRevenue: true, + currency: ad.currency || 'USD', + dealId: null, + meta: { + mediaType: BANNER + }, + ad: ad.html || null + }; +}; + +const getContextParams = (bidRequests, bidderRequest) => { + const bid = bidRequests[0]; + const { params } = bid; + const requestParams = { + site: params.site, + area: params.area, + cre_format: 'html', + systems: 'das', + kvprver: VERSION, + ems_url: 1, + bid_rate: 1, + ...parseParams(params, bidderRequest) + }; + return Object.keys(requestParams).map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(requestParams[key])).join('&'); +}; + +const getSlots = (bidRequests) => { + let queryString = ''; + const batchSize = bidRequests.length; + for (let i = 0; i < batchSize; i++) { + const adunit = bidRequests[i]; + const slotSequence = utils.deepAccess(adunit, 'params.slotSequence'); + + const sizes = parseSizesInput(getAdUnitSizes(adunit)).join(','); + + queryString += `&slot${i}=${encodeURIComponent(adunit.params.slot)}&id${i}=${encodeURIComponent(adunit.bidId)}&composition${i}=CHILD`; + + if (sizes.length) { + queryString += `&iusizes${i}=${encodeURIComponent(sizes)}`; + } + if (slotSequence !== undefined) { + queryString += `&pos${i}=${encodeURIComponent(slotSequence)}`; + } + } + return queryString; +}; + +const getGdprParams = (bidderRequest) => { + const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); + let consentString = deepAccess(bidderRequest, 'gdprConsent.consentString'); + let queryString = ''; + if (gdprApplies !== undefined) { + queryString += `&gdpr_applies=${encodeURIComponent(gdprApplies)}`; + } + if (consentString !== undefined) { + queryString += `&euconsent=${encodeURIComponent(consentString)}`; + } + return queryString; +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bidRequest) { + if (!bidRequest || !bidRequest.params || typeof bidRequest.params !== 'object') { + return; + } + const { params } = bidRequest; + return Boolean(params.network && params.site && params.area && params.slot); + }, + + buildRequests: function (bidRequests, bidderRequest) { + const slotsQuery = getSlots(bidRequests); + const contextQuery = getContextParams(bidRequests, bidderRequest); + const gdprQuery = getGdprParams(bidderRequest); + const bidIds = bidRequests.map((bid) => ({ slot: bid.params.slot, bidId: bid.bidId })); + const network = bidRequests[0].params.network; + return [{ + method: 'GET', + url: getEndpoint(network) + contextQuery + slotsQuery + gdprQuery, + bidIds: bidIds + }]; + }, + + interpretResponse: function (serverResponse, bidRequest) { + const response = serverResponse.body; + if (!response || !response.ads || response.ads.length === 0) { + return []; + } + return response.ads.map(buildBid).filter((bid) => !isEmpty(bid)); + } +}; + +registerBidder(spec); diff --git a/modules/rasBidAdapter.md b/modules/rasBidAdapter.md new file mode 100644 index 00000000000..384ba0b611f --- /dev/null +++ b/modules/rasBidAdapter.md @@ -0,0 +1,51 @@ +# Overview + +``` +Module Name: Ringier Axel Springer Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@ringpublishing.com +``` + +# Description + +Module that connects to Ringer Axel Springer demand sources. +Only banner format is supported. + +# Test Parameters +```js +var adUnits = [{ + code: 'test-div-ad', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'ras', + params: { + network: '4178463', + site: 'test', + area: 'areatest', + slot: 'slot' + } + }] +}]; +``` + +# Parameters + +| Name | Scope | Type | Description | Example +| --- | --- | --- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| --- +| network | required | String | Specific identifier provided by RAS | `"4178463"` +| site | required | String | Specific identifier name (case-insensitive) that is associated with this ad unit and provided by RAS | `"example_com"` +| area | required | String | Ad unit category name; only case-insensitive alphanumeric with underscores and hyphens are allowed | `"sport"` +| slot | required | String | Ad unit placement name (case-insensitive) provided by RAS | `"slot"` +| slotSequence | optional | Number | Ad unit sequence position provided by RAS | `1` +| pageContext | optional | Object | Web page context data | `{}` +| pageContext.dr | optional | String | Document referrer URL address | `"https://example.com/"` +| pageContext.du | optional | String | Document URL address | `"https://example.com/sport/football/article.html?id=932016a5-02fc-4d5c-b643-fafc2f270f06"` +| pageContext.dv | optional | String | Document virtual address as slash-separated path that may consist of any number of parts (case-insensitive alphanumeric with underscores and hyphens); first part should be the same as `site` value and second as `area` value; next parts may reflect website navigation | `"example_com/sport/football"` +| pageContext.keyWords | optional | String[] | List of keywords associated with this ad unit; only case-insensitive alphanumeric with underscores and hyphens are allowed | `["euro", "lewandowski"]` +| pageContext.keyValues | optional | Object | Key-values associated with this ad unit (case-insensitive); following characters are not allowed in the values: `" ' = ! + # * ~ ; ^ ( ) < > [ ] & @` | `{}` +| pageContext.keyValues.ci | optional | String | Content unique identifier | `"932016a5-02fc-4d5c-b643-fafc2f270f06"` +| pageContext.keyValues.adunit | optional | String | Ad unit name | `"example_com/sport"` diff --git a/modules/rdnBidAdapter.md b/modules/rdnBidAdapter.md deleted file mode 100644 index 9082c95c520..00000000000 --- a/modules/rdnBidAdapter.md +++ /dev/null @@ -1,33 +0,0 @@ -# Overview - -``` -Module Name: RDN Bidder Adapter -Module Type: Bidder Adapter -Maintainer: engineer@lob-inc.com -``` - -# Description - -Connect to RDN for bids. - -RDN bid adapter supports Banner currently. - -# Test Parameters - -``` - var adUnits = [ - { - code: 'test-ad-div', - sizes: [[300, 250]], - mediaTypes: {banner: {}}, - bids: [ - { - bidder: 'rdn', - params: { - adSpotId: '10000' - } - } - ] - } - ]; -``` diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js index 099e1fb6332..1572a3df0df 100644 --- a/modules/readpeakBidAdapter.js +++ b/modules/readpeakBidAdapter.js @@ -1,7 +1,8 @@ -import { logError, replaceAuctionPrice, parseUrl } from '../src/utils.js'; +import { logError, replaceAuctionPrice, triggerPixel, isStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { NATIVE, BANNER } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; export const ENDPOINT = 'https://app.readpeak.com/header/prebid'; @@ -24,6 +25,9 @@ export const spec = { isBidRequestValid: bid => !!(bid && bid.params && bid.params.publisherId), buildRequests: (bidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); + const currencyObj = config.getConfig('currency'); const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; @@ -64,8 +68,16 @@ export const spec = { }; }, - interpretResponse: (response, request) => - bidResponseAvailable(request, response) + interpretResponse: (response, request) => { + return bidResponseAvailable(request, response) + }, + + onBidWon: (bid) => { + if (bid.burl && isStr(bid.burl)) { + bid.burl = replaceAuctionPrice(bid.burl, bid.cpm); + triggerPixel(bid.burl); + } + }, }; function bidResponseAvailable(bidRequest, bidResponse) { @@ -103,6 +115,7 @@ function bidResponseAvailable(bidRequest, bidResponse) { bid.ad = idToBidMap[id].adm bid.width = idToBidMap[id].w bid.height = idToBidMap[id].h + bid.burl = idToBidMap[id].burl } if (idToBidMap[id].adomain) { bid.meta = { @@ -238,12 +251,6 @@ function bannerImpression(slot) { } function site(bidRequests, bidderRequest) { - const url = - config.getConfig('pageUrl') || - (bidderRequest && - bidderRequest.refererInfo && - bidderRequest.refererInfo.referer); - const pubId = bidRequests && bidRequests.length > 0 ? bidRequests[0].params.publisherId @@ -255,12 +262,11 @@ function site(bidRequests, bidderRequest) { return { publisher: { id: pubId.toString(), - domain: config.getConfig('publisherDomain') + domain: bidderRequest?.refererInfo?.domain, }, id: siteId ? siteId.toString() : pubId.toString(), - page: url, - domain: - (url && parseUrl(url).hostname) || config.getConfig('publisherDomain') + page: bidderRequest?.refererInfo?.page, + domain: bidderRequest?.refererInfo?.domain }; } return undefined; diff --git a/modules/realvuAnalyticsAdapter.js b/modules/realvuAnalyticsAdapter.js index 832e907893c..0811c70a2f7 100644 --- a/modules/realvuAnalyticsAdapter.js +++ b/modules/realvuAnalyticsAdapter.js @@ -1,5 +1,5 @@ // RealVu Analytics Adapter -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { getStorageManager } from '../src/storageManager.js'; diff --git a/modules/reklamstoreBidAdapter.md b/modules/reklamstoreBidAdapter.md deleted file mode 100644 index 8615341f5cc..00000000000 --- a/modules/reklamstoreBidAdapter.md +++ /dev/null @@ -1,32 +0,0 @@ -# Overview - -Module Name: ReklamStore Bidder Adapter -Module Type: Bidder Adapter -Maintainer: it@reklamstore.com - -# Description - -Module that connects to ReklamStore's demand sources. - -ReklamStore supports display. - - -# Test Parameters -# display -``` - - var adUnits = [ - { - code: 'banner-ad-div', - sizes: [[300, 250]], - bids: [ - { - bidder: 'reklamstore', - params: { - regionId:532211 - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index 8312dacbdbb..347cf66ee2b 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -1,12 +1,13 @@ -import { deepAccess, logWarn, getBidIdParameter, parseQueryStringParameters, triggerPixel, generateUUID, isArray } from '../src/utils.js'; +import { deepAccess, logWarn, getBidIdParameter, parseQueryStringParameters, triggerPixel, generateUUID, isArray, isNumber, parseSizesInput } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import { getStorageManager } from '../src/storageManager.js'; +import sha1 from 'crypto-js/sha1'; const BIDDER_CODE = 'relaido'; const BIDDER_DOMAIN = 'api.relaido.jp'; -const ADAPTER_VERSION = '1.0.7'; +const ADAPTER_VERSION = '1.1.0'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; @@ -44,7 +45,10 @@ function buildRequests(validBidRequests, bidderRequest) { let height = 0; if (hasVideoMediaType(bidRequest) && isVideoValid(bidRequest)) { - const playerSize = getValidSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')); + let playerSize = getValidSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')); + if (playerSize.length === 0) { + playerSize = getValidSizes(deepAccess(bidRequest, 'params.video.playerSize')); + } width = playerSize[0][0]; height = playerSize[0][1]; mediaType = VIDEO; @@ -67,11 +71,11 @@ function buildRequests(validBidRequests, bidderRequest) { } if (!bidder) { - bidder = bidRequest.bidder + bidder = bidRequest.bidder; } if (!bidder) { - bidder = bidRequest.bidder + bidder = bidRequest.bidder; } if (!count) { @@ -88,6 +92,7 @@ function buildRequests(validBidRequests, bidderRequest) { player: bidRequest.params.player, width: width, height: height, + banner_sizes: getBannerSizes(bidRequest), media_type: mediaType }); } @@ -101,8 +106,9 @@ function buildRequests(validBidRequests, bidderRequest) { uuid: getUuid(), pv: '$prebid.version$', imuid: imuid, - ref: bidderRequest.refererInfo.referer - }) + canonical_url_hash: getCanonicalUrlHash(bidderRequest.refererInfo), + ref: bidderRequest.refererInfo.page + }); return { method: 'POST', @@ -134,20 +140,24 @@ function interpretResponse(serverResponse, bidRequest) { dealId: body.dealId || '', ttl: body.ttl || DEFAULT_TTL, netRevenue: true, - mediaType: res.mediaType || VIDEO, meta: { advertiserDomains: res.adomain || [], mediaType: VIDEO } }; - if (bidResponse.mediaType === VIDEO) { + if (res.vast && res.mediaType === VIDEO) { + bidResponse.mediaType = VIDEO; bidResponse.vastXml = res.vast; bidResponse.renderer = newRenderer(res.bidId, playerUrl); - } else { + } else if (res.vast && res.mediaType === BANNER) { + bidResponse.mediaType = BANNER; const playerTag = createPlayerTag(playerUrl); const renderTag = createRenderTag(res.width, res.height, res.vast); bidResponse.ad = `
${playerTag}${renderTag}
`; + } else if (res.adTag) { + bidResponse.mediaType = BANNER; + bidResponse.ad = decodeURIComponent(res.adTag); } bidResponses.push(bidResponse); } @@ -241,9 +251,6 @@ function outstreamRender(bid) { } function isBannerValid(bid) { - if (!isMobile()) { - return false; - } const sizes = getValidSizes(deepAccess(bid, 'mediaTypes.banner.sizes')); if (sizes.length > 0) { return true; @@ -252,7 +259,10 @@ function isBannerValid(bid) { } function isVideoValid(bid) { - const playerSize = getValidSizes(deepAccess(bid, 'mediaTypes.video.playerSize')); + let playerSize = getValidSizes(deepAccess(bid, 'mediaTypes.video.playerSize')); + if (playerSize.length === 0) { + playerSize = getValidSizes(deepAccess(bid, 'params.video.playerSize')); + } if (playerSize.length > 0) { const context = deepAccess(bid, 'mediaTypes.video.context'); if (context && context === 'outstream') { @@ -270,12 +280,12 @@ function getUuid() { return newId; } -export function isMobile() { - const ua = navigator.userAgent; - if (ua.indexOf('iPhone') > -1 || ua.indexOf('iPod') > -1 || (ua.indexOf('Android') > -1 && ua.indexOf('Tablet') == -1)) { - return true; +function getCanonicalUrlHash(refererInfo) { + const canonicalUrl = refererInfo.canonicalUrl || null; + if (!canonicalUrl) { + return null; } - return false; + return sha1(canonicalUrl).toString(); } function hasBannerMediaType(bid) { @@ -299,12 +309,32 @@ function getValidSizes(sizes) { if ((width >= 300 && height >= 250)) { result.push([width, height]); } + } else if (isNumber(sizes[i])) { + const width = sizes[0]; + const height = sizes[1]; + if (width == 1 && height == 1) { + return [[1, 1]]; + } + if ((width >= 300 && height >= 250)) { + return [[width, height]]; + } } } } return result; } +function getBannerSizes(bidRequest) { + if (!hasBannerMediaType(bidRequest)) { + return null; + } + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); + if (!isArray(sizes)) { + return null; + } + return parseSizesInput(sizes).join(','); +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], diff --git a/modules/relevantAnalyticsAdapter.js b/modules/relevantAnalyticsAdapter.js index 5917262c810..7774370f7ad 100644 --- a/modules/relevantAnalyticsAdapter.js +++ b/modules/relevantAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; const relevantAnalytics = adapter({ analyticsType: 'bundle', handler: 'on' }); diff --git a/modules/reloadBidAdapter.md b/modules/reloadBidAdapter.md deleted file mode 100644 index 42fe11b40b3..00000000000 --- a/modules/reloadBidAdapter.md +++ /dev/null @@ -1,48 +0,0 @@ -# Overview - -Module Name: Reload Bid Adapter - -Module Type: Bidder Adapter - -Maintainer: prebid@reload.net - -# Description - -Prebid module for connecting to Reload - -# Parameters -## Banner - -| Name | Scope | Description | Example | -| :------------ | :------- | :---------------------------------------------- | :--------------------------------- | -| `plcmID` | required | Placement ID (provided by Reload) | "4234897234" | -| `partID` | required | Partition ID (provided by Reload) | "part_01" | -| `opdomID` | required | Internal parameter (provided by Reload) | 0 | -| `bsrvID` | required | Internal parameter (provided by Reload) | 12 | -| `type` | optional | Internal parameter (provided by Reload) | "pcm" | - -# Example ad units -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [ - [300, 250] - ], - } - }, - bids: [{ - bidder: 'reload', - params: { - plcmID: 'prebid_check', - partID: 'part_4', - opdomID: '0', - bsrvID: 0, - type: 'pcm' - } - }] - }]; \ No newline at end of file diff --git a/modules/resetdigitalBidAdapter.js b/modules/resetdigitalBidAdapter.js index 255ee32629c..a606f0c0b7d 100644 --- a/modules/resetdigitalBidAdapter.js +++ b/modules/resetdigitalBidAdapter.js @@ -1,5 +1,6 @@ -import { timestamp, deepAccess, getOrigin } from '../src/utils.js'; +import { timestamp, deepAccess } from '../src/utils.js'; +import { getOrigin } from '../libraries/getOrigin/index.js'; import { config } from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'resetdigital'; @@ -24,9 +25,11 @@ export const spec = { site: { domain: getOrigin(), iframe: !bidderRequest.refererInfo.reachedTop, + // TODO: the last element in refererInfo.stack is window.location.href, that's unlikely to have been the intent here url: stack && stack.length > 0 ? [stack.length - 1] : null, https: (window.location.protocol === 'https:'), - referrer: bidderRequest.refererInfo.referer + // TODO: is 'page' the right value here? + referrer: bidderRequest.refererInfo.page }, imps: [], user_ids: validBidRequests[0].userId, diff --git a/modules/resultsmediaBidAdapter.md b/modules/resultsmediaBidAdapter.md deleted file mode 100644 index 0b65264c8e4..00000000000 --- a/modules/resultsmediaBidAdapter.md +++ /dev/null @@ -1,47 +0,0 @@ -# Overview - -``` -Module Name: ResultsMedia (resultsmedia.com) Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid@resultsmedia.COM -``` - -# Description - -Prebid adapter for ResultsMedia RTB. Requires approval and account setup. - -# Test Parameters - -## Web -``` - var adUnits = [{ - code: 'banner-ad-div', - mediaTypes: { - banner: { - sizes: [ - [300, 200] // banner sizes - ], - } - }, - bids: [{ - bidder: 'resultsmedia', - params: { - zoneId: 9999 - } - }] - }, { - code: 'video-ad-player', - mediaTypes: { - video: { - context: 'instream', // or 'outstream' - playerSize: [640, 480] // video player size - } - }, - bids: [{ - bidder: 'resultsmedia', - params: { - zoneId: 9999 - } - }] - }]; -``` \ No newline at end of file diff --git a/modules/revcontentBidAdapter.js b/modules/revcontentBidAdapter.js index 0888e5ad1b4..5e33a9a0fd7 100644 --- a/modules/revcontentBidAdapter.js +++ b/modules/revcontentBidAdapter.js @@ -4,6 +4,8 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { triggerPixel, isFn, deepAccess, getAdUnitSizes, parseGPTSingleSizeArrayToRtbSize, _map } from '../src/utils.js'; +import {parseDomain} from '../src/refererDetection.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'revcontent'; const NATIVE_PARAMS = { @@ -31,6 +33,9 @@ export const spec = { return (typeof bid.params.apiKey !== 'undefined' && typeof bid.params.userId !== 'undefined'); }, buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const userId = validBidRequests[0].params.userId; const widgetId = validBidRequests[0].params.widgetId; const apiKey = validBidRequests[0].params.apiKey; @@ -44,11 +49,11 @@ export const spec = { let serverRequests = []; var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { - refererInfo = bidderRequest.refererInfo.referer; + refererInfo = bidderRequest.refererInfo.page; } if (typeof domain === 'undefined') { - domain = extractHostname(refererInfo); + domain = parseDomain(refererInfo, {noPort: true}); } var endpoint = 'https://' + host + '/rtb?apiKey=' + apiKey + '&userId=' + userId; @@ -196,23 +201,6 @@ function getTemplate(size, customTemplate) { return ''; } -function extractHostname(url) { - if (typeof url == 'undefined' || url == null) { - return ''; - } - var hostname; - if (url.indexOf('//') > -1) { - hostname = url.split('/')[2]; - } else { - hostname = url.split('/')[0]; - } - - hostname = hostname.split(':')[0]; - hostname = hostname.split('?')[0]; - - return hostname; -} - function buildImp(bid, id) { let bidfloor; if (isFn(bid.getFloor)) { @@ -244,7 +232,7 @@ function buildImp(bid, id) { w: sizes[0][0], h: sizes[0][1], format: sizes.map(wh => parseGPTSingleSizeArrayToRtbSize(wh)), - } + }; } else if (nativeReq) { const assets = _map(bid.nativeParams, (bidParams, key) => { const props = NATIVE_PARAMS[key]; diff --git a/modules/rexrtbBidAdapter.md b/modules/rexrtbBidAdapter.md deleted file mode 100644 index 1cb937b0a3d..00000000000 --- a/modules/rexrtbBidAdapter.md +++ /dev/null @@ -1,32 +0,0 @@ -# Overview - -Module Name: REXRTB Bidder Adapter - -Module Type: Bidder Adapter - -Maintainer: tech@rexrtb.com - - -# Description - -Module that connects to REXRTB's demand source - -# Test Parameters -```javascript - var adUnits = [ - { - code: 'test-ad', - sizes: [[728, 98]], - bids: [ - { - bidder: 'rexrtb', - params: { - id: 89, - token: '658f11a5efbbce2f9be3f1f146fcbc22', - source: 'prebidtest' - } - } - ] - }, - ]; -``` \ No newline at end of file diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index 9e378f2d2ed..94b3645f57b 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -62,25 +62,11 @@ function RhythmOneBidAdapter() { } function frameSite(bidderRequest) { - var site = { - domain: '', - page: '', - ref: '' - } - if (bidderRequest && bidderRequest.refererInfo) { - var ri = bidderRequest.refererInfo; - site.ref = ri.referer; - - if (ri.stack.length) { - site.page = ri.stack[ri.stack.length - 1]; - - // clever trick to get the domain - var el = document.createElement('a'); - el.href = ri.stack[0]; - site.domain = el.hostname; - } + return { + domain: bidderRequest?.refererInfo?.domain || '', + page: bidderRequest?.refererInfo?.page || '', + ref: bidderRequest?.refererInfo?.ref || '' } - return site; } function frameDevice() { diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index b49d7c5584c..1faed74d84e 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -43,7 +43,8 @@ export const spec = { bidderRequestId: bid.bidderRequestId, tagId: bid.adUnitCode, sizes: raiGetSizes(bid), - referer: (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null), + // TODO: is 'page' the right value here? + referer: (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null), numIframes: (typeof bidderRequest.refererInfo.numIframes != 'undefined' ? bidderRequest.refererInfo.numIframes : null), transactionId: bid.transactionId, timeout: config.getConfig('bidderTimeout'), @@ -56,7 +57,8 @@ export const spec = { schain: bid.schain }; - REFERER = (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null) + // TODO: is 'page' the right value here? + REFERER = (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null) payload.gdpr_consent = ''; payload.gdpr = false; @@ -130,7 +132,7 @@ export const spec = { bidResponses.push(bidResponse); } - return bidResponses + return bidResponses; }, /*** * User Syncs diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index a8ea023d46a..c1360b79066 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -8,7 +8,7 @@ const BIDDER_CODE = 'rise'; const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const CURRENCY = 'USD'; -const SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; +const DEFAULT_SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' @@ -42,13 +42,14 @@ export const spec = { // use data from the first bid, to create the general params for all bids const generalObject = validBidRequests[0]; const testMode = generalObject.params.testMode; + const rtbDomain = generalObject.params.rtbDomain; combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); return { method: 'POST', - url: getEndpoint(testMode), + url: getEndpoint(testMode, rtbDomain), data: combinedRequestsObject } }, @@ -223,9 +224,11 @@ function isSyncMethodAllowed(syncRule, bidderCode) { /** * Get the seller endpoint * @param testMode {boolean} + * @param rtbDomain {string} * @returns {string} */ -function getEndpoint(testMode) { +function getEndpoint(testMode, rtbDomain) { + const SELLER_ENDPOINT = rtbDomain ? `https://${rtbDomain}/` : DEFAULT_SELLER_ENDPOINT; return testMode ? SELLER_ENDPOINT + MODES.TEST : SELLER_ENDPOINT + MODES.PRODUCTION; @@ -287,6 +290,7 @@ function generateBidParameters(bid, bidderRequest) { floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), bidId: getBidIdParameter('bidId', bid), bidderRequestId: getBidIdParameter('bidderRequestId', bid), + loop: getBidIdParameter('bidderRequestsCount', bid), transactionId: getBidIdParameter('transactionId', bid), }; @@ -383,14 +387,14 @@ function generateGeneralParams(generalObject, bidderRequest) { ua: navigator.userAgent, session_id: getBidIdParameter('auctionId', generalObject), tmax: timeout - } + }; const userIdsParam = getBidIdParameter('userId', generalObject); if (userIdsParam) { generalParams.userIds = JSON.stringify(userIdsParam); } - const ortb2Metadata = config.getConfig('ortb2') || {}; + const ortb2Metadata = bidderRequest.ortb2 || {}; if (ortb2Metadata.site) { generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); } @@ -423,9 +427,11 @@ function generateGeneralParams(generalObject, bidderRequest) { } if (bidderRequest && bidderRequest.refererInfo) { - generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); - generalParams.page_url = config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + // TODO: is 'ref' the right value here? + generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); + // TODO: does the fallback make sense here? + generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); } - return generalParams + return generalParams; } diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 83f8adfd645..2920fd28528 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -24,6 +24,7 @@ The adapter supports Video(instream). | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 | `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | false +| `rtbDomain` | optional | String | Sets the seller end point | "www.test.com" # Test Parameters ```javascript @@ -43,7 +44,8 @@ var adUnits = [ org: '56f91cd4d3e3660002000033', // Required floorPrice: 2.00, // Optional placementId: '12345678', // Optional - testMode: false // Optional + testMode: false // Optional, + rtbDomain: "www.test.com" //Optional } }] } diff --git a/modules/rivrAnalyticsAdapter.js b/modules/rivrAnalyticsAdapter.js index 279b1b13051..cf14ff44571 100644 --- a/modules/rivrAnalyticsAdapter.js +++ b/modules/rivrAnalyticsAdapter.js @@ -1,5 +1,5 @@ import {ajax} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; diff --git a/modules/rockyouBidAdapter.md b/modules/rockyouBidAdapter.md deleted file mode 100644 index 1c6d2708b99..00000000000 --- a/modules/rockyouBidAdapter.md +++ /dev/null @@ -1,58 +0,0 @@ -# Overview - -``` -Module Name: RockYou Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid.adapter@rockyou.com -``` - -# Description - -Connects to the RockYou exchange for bids. - -The RockYou bid adapter supports Banner and Video. - -For publishers who wish to be set up on the RockYou Ad Network, please contact -publishers@rockyou.com. - -RockYou user syncing requires the `userSync.iframeEnabled` property be set to `true`. - -# Test PARAMETERS -``` -var adUnits = [ - - // Banner adUnit - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[720, 480]] - } - }, - - bids: [{ - bidder: 'rockyou', - params: { - placementId: '4954' - } - }] - }, - - // Video (outstream) - { - code: 'video-outstream', - mediaTypes: { - video: { - context: 'outstream', - playerSize: [720, 480] - } - }, - bids: [{ - bidder: 'rockyou', - params: { - placementId: '4957' - } - }] - } -] -``` diff --git a/modules/roxotAnalyticsAdapter.js b/modules/roxotAnalyticsAdapter.js index b11898b9ea8..1ded17f3a5b 100644 --- a/modules/roxotAnalyticsAdapter.js +++ b/modules/roxotAnalyticsAdapter.js @@ -1,5 +1,5 @@ import {deepClone, getParameterByName, logError, logInfo} from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {includes} from '../src/polyfill.js'; diff --git a/modules/rtbdemandBidAdapter.md b/modules/rtbdemandBidAdapter.md deleted file mode 100644 index 2727d85e084..00000000000 --- a/modules/rtbdemandBidAdapter.md +++ /dev/null @@ -1,26 +0,0 @@ -# Overview - -**Module Name**: Rtbdemand Media fmxSSP Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: rtb@rtbdemand.com - -# Description - -Connects to Rtbdemand Media fmxSSP demand source to fetch bids. - -# Test Parameters -``` - var adUnits = [{ - code: 'banner-ad-div', - sizes: [[300, 250]], - bids: [{ - bidder: 'rtbdemand', - params: { - zoneid: '9999', - floor: 0.005, - server: 'bidding.rtbdemand.com' - } - }] - }]; - -``` diff --git a/modules/rtbdemandadkBidAdapter.md b/modules/rtbdemandadkBidAdapter.md deleted file mode 100644 index 96bd3f6c8d7..00000000000 --- a/modules/rtbdemandadkBidAdapter.md +++ /dev/null @@ -1,45 +0,0 @@ -# Overview - -``` -Module Name: Rtbdemandadk Bidder Adapter -Module Type: Bidder Adapter -Maintainer: shreyanschopra@rtbdemand.com -``` - -# Description - -Connects to Rtbdemandadk whitelabel platform. -Banner and video formats are supported. - - -# Test Parameters -``` - var adUnits = [ - { - code: 'banner-ad-div', - sizes: [[300, 250]], // banner size - bids: [ - { - bidder: 'rtbdemandadk', - params: { - zoneId: '30164', //required parameter - host: 'cpm.metaadserving.com' //required parameter - } - } - ] - }, { - code: 'video-ad-player', - sizes: [640, 480], // video player size - bids: [ - { - bidder: 'rtbdemandadk', - mediaType : 'video', - params: { - zoneId: '30164', //required parameter - host: 'cpm.metaadserving.com' //required parameter - } - } - ] - } - ]; -``` diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index b8436179a30..fdf64483da7 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -1,7 +1,9 @@ -import {deepAccess, getOrigin, isArray, logError} from '../src/utils.js'; +import {deepAccess, isArray, logError} from '../src/utils.js'; +import { getOrigin } from '../libraries/getOrigin/index.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {includes} from '../src/polyfill.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'rtbhouse'; const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia']; @@ -43,6 +45,9 @@ export const spec = { return !!(includes(REGIONS, bid.params.region) && bid.params.publisherId); }, buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const request = { id: validBidRequests[0].auctionId, imp: validBidRequests.map(slot => mapImpression(slot)), @@ -63,7 +68,7 @@ export const spec = { if (schain) { request.ext = { schain: schain, - } + }; } } @@ -76,6 +81,30 @@ export const spec = { } } + const ortb2Params = bidderRequest && bidderRequest.ortb2; + if (ortb2Params?.user) { + request.user = { + ...request.user, + ...(ortb2Params.user.data && { + data: { ...request.user?.data, ...ortb2Params.user.data }, + }), + ...(ortb2Params.user.ext && { + ext: { ...request.user?.ext, ...ortb2Params.user.ext }, + }), + }; + } + if (ortb2Params?.site) { + request.site = { + ...request.site, + ...(ortb2Params.site.content && { + content: { ...request.site?.content, ...ortb2Params.site.content }, + }), + ...(ortb2Params.site.ext && { + ext: { ...request.site?.ext, ...ortb2Params.site.ext }, + }), + }; + } + return { method: 'POST', url: 'https://' + validBidRequests[0].params.region + '.' + ENDPOINT_URL, @@ -180,7 +209,7 @@ function mapSite(slot, bidderRequest) { publisher: { id: pubId.toString(), }, - page: bidderRequest.refererInfo.referer, + page: bidderRequest.refererInfo.page, name: getOrigin() }; if (channel) { diff --git a/modules/rtbsapeBidAdapter.js b/modules/rtbsapeBidAdapter.js index d58b3a1f240..cf3d79eb377 100644 --- a/modules/rtbsapeBidAdapter.js +++ b/modules/rtbsapeBidAdapter.js @@ -43,7 +43,8 @@ export const spec = { requestId: bidderRequest.bidderRequestId, bids: validBidRequests, timezone: (tz > 0 ? '-' : '+') + padInt(Math.floor(Math.abs(tz) / 60)) + ':' + padInt(Math.abs(tz) % 60), - refererInfo: bidderRequest.refererInfo + // TODO: please do not send internal data structures over the network + refererInfo: bidderRequest.refererInfo.legacy }, } }, diff --git a/modules/rtbsolutionsBidAdapter.md b/modules/rtbsolutionsBidAdapter.md deleted file mode 100644 index e671de306a0..00000000000 --- a/modules/rtbsolutionsBidAdapter.md +++ /dev/null @@ -1,128 +0,0 @@ -# Overview - -Module Name: Rtbsolutions Bidder Adapter -Module Type: Bidder Adapter -Maintainer: info@rtbsolutions.pro - -# Description - -You can use this adapter to get a bid from rtbsolutions. - -About us: http://rtbsolutions.pro - - -# Test Parameters -```javascript - var adUnits = [ - { - code: '/19968336/header-bid-tag-1', - mediaTypes: { - banner: { - sizes: sizes - } - }, - bids: [{ - bidder: 'rtbsolutions', - params: { - blockId: 777, - s1: 'sub_1', - s2: 'sub_2', - s3: 'sub_3', - s4: 'sub_4' - } - }] - }]; -``` - -Where: - -* blockId - Block ID from platform (required) -* s1..s4 - Sub Id (optional) - -# Example page - -```html - - - - - Prebid - - - - - -

Prebid

-
Div-1
-
- -
- - -``` diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 381059c68f7..eafbe7b472c 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -152,13 +152,12 @@ */ import {config} from '../../src/config.js'; -import {module} from '../../src/hook.js'; +import {getHook, module} from '../../src/hook.js'; import {logError, logInfo, logWarn} from '../../src/utils.js'; import * as events from '../../src/events.js'; import CONSTANTS from '../../src/constants.json'; import {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js'; import {find} from '../../src/polyfill.js'; -import {getGlobal} from '../../src/prebidGlobal.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; @@ -229,7 +228,7 @@ export function init(config) { _moduleConfig = realTimeData; _dataProviders = realTimeData.dataProviders; setEventsListeners(); - getGlobal().requestBids.before(setBidRequestsData, 40); + getHook('startAuction').before(setBidRequestsData, 20); // RTD should run before FPD initSubModules(); }); } diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 69335ff33a8..3ad1bb26b47 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -1,5 +1,5 @@ import { generateUUID, mergeDeep, deepAccess, parseUrl, logError, pick, isEmpty, logWarn, debugTurnedOn, parseQS, getWindowLocation, isAdUnitCodeMatchingSlot, isNumber, isGptPubadsDefined, _each, deepSetValue, deepClone, logInfo } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; @@ -148,7 +148,7 @@ function sendBillingEvent(event) { function getBasicEventDetails(auctionId, trigger) { let auctionCache = cache.auctions[auctionId]; - let referrer = config.getConfig('pageUrl') || pageReferer || (auctionCache && auctionCache.referrer); + let referrer = pageReferer || (auctionCache && auctionCache.referrer); let message = { timestamps: { prebidLoaded: rubiconAdapter.MODULE_INITIALIZED_TIME, @@ -676,7 +676,8 @@ let rubiconAdapter = Object.assign({}, baseAdapter, { cacheEntry.bids = {}; cacheEntry.bidsWon = {}; cacheEntry.gamHasRendered = {}; - cacheEntry.referrer = pageReferer = deepAccess(args, 'bidderRequests.0.refererInfo.referer'); + // TODO: is 'page' the right value here? + cacheEntry.referrer = pageReferer = deepAccess(args, 'bidderRequests.0.refererInfo.page'); cacheEntry.bidderOrder = []; const floorData = deepAccess(args, 'bidderRequests.0.bids.0.floorData'); if (floorData) { diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 48c5ddf813a..4817d62348a 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -317,14 +317,14 @@ export const spec = { applyFPD(bidRequest, VIDEO, data); - // if storedAuctionResponse has been set, pass SRID - if (bidRequest.storedAuctionResponse) { - deepSetValue(data.imp[0], 'ext.prebid.storedauctionresponse.id', bidRequest.storedAuctionResponse.toString()); - } - // set ext.prebid.auctiontimestamp using auction time deepSetValue(data.imp[0], 'ext.prebid.auctiontimestamp', bidderRequest.auctionStart); + // set storedrequests to undefined so not sent to PBS + // top level and imp level both 'ext.prebid' objects are set above so no exception thrown here + data.ext.prebid.storedrequest = undefined; + data.imp[0].ext.prebid.storedrequest = undefined; + return { method: 'POST', url: `https://${rubiConf.videoHost || 'prebid-server'}.rubiconproject.com/openrtb2/auction`, @@ -836,11 +836,11 @@ function _getScreenResolution() { * @returns {string} */ function _getPageUrl(bidRequest, bidderRequest) { - let pageUrl = config.getConfig('pageUrl'); + let pageUrl; if (bidRequest.params.referrer) { pageUrl = bidRequest.params.referrer; - } else if (!pageUrl) { - pageUrl = bidderRequest.refererInfo.referer; + } else { + pageUrl = bidderRequest.refererInfo.page; } return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; } @@ -939,7 +939,7 @@ function parseSizes(bid, mediaType) { } else if (typeof deepAccess(bid, 'mediaTypes.banner.sizes') !== 'undefined') { sizes = mapSizes(bid.mediaTypes.banner.sizes); } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizes = mapSizes(bid.sizes) + sizes = mapSizes(bid.sizes); } else { logWarn('Rubicon: no sizes are setup or found'); } @@ -1014,8 +1014,9 @@ function applyFPD(bidRequest, mediaType, data) { if (bidRequest.params.keywords) BID_FPD.site.keywords = (isArray(bidRequest.params.keywords)) ? bidRequest.params.keywords.join(',') : bidRequest.params.keywords; - let fpd = mergeDeep({}, config.getConfig('ortb2') || {}, BID_FPD); - let impData = deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; + let fpd = mergeDeep({}, bidRequest.ortb2 || {}, BID_FPD); + let impExt = deepAccess(bidRequest.ortb2Imp, 'ext') || {}; + let impExtData = deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); const SEGTAX = {user: [4], site: [1, 2, 5, 6]}; @@ -1031,7 +1032,7 @@ function applyFPD(bidRequest, mediaType, data) { if (segments.length > 0) return segments.toString(); }).toString(); } else if (typeof prop === 'object' && !Array.isArray(prop)) { - logWarn('Rubicon: Filtered FPD key: ', key, ': Expected value to be string, integer, or an array of strings/ints'); + return undefined; } else if (typeof prop !== 'undefined') { return (Array.isArray(prop)) ? prop.filter(value => { if (typeof value !== 'object' && typeof value !== 'undefined') return value.toString(); @@ -1044,7 +1045,7 @@ function applyFPD(bidRequest, mediaType, data) { let val = validate(obj, key, name); let loc = (MAP[key] && isParent) ? `${MAP[key]}` : (key === 'data') ? `${MAP[name]}iab` : `${MAP[name]}${key}`; data[loc] = (data[loc]) ? data[loc].concat(',', val) : val; - } + }; if (mediaType === BANNER) { ['site', 'user'].forEach(name => { @@ -1060,11 +1061,11 @@ function applyFPD(bidRequest, mediaType, data) { } }); }); - Object.keys(impData).forEach((key) => { + Object.keys(impExtData).forEach((key) => { if (key !== 'adserver') { - addBannerData(impData[key], 'site', key); - } else if (impData[key].name === 'gam') { - addBannerData(impData[key].adslot, name, key) + addBannerData(impExtData[key], 'site', key); + } else if (impExtData[key].name === 'gam') { + addBannerData(impExtData[key].adslot, name, key) } }); @@ -1078,8 +1079,8 @@ function applyFPD(bidRequest, mediaType, data) { delete data['tg_i.dfp_ad_unit_code']; } } else { - if (Object.keys(impData).length) { - mergeDeep(data.imp[0].ext, {data: impData}); + if (Object.keys(impExt).length) { + mergeDeep(data.imp[0].ext, impExt); } // add in gpid if (gpid) { diff --git a/modules/saambaaBidAdapter.js b/modules/saambaaBidAdapter.js index 36ab50bfddd..5300fe879b0 100644 --- a/modules/saambaaBidAdapter.js +++ b/modules/saambaaBidAdapter.js @@ -1,3 +1,5 @@ +// TODO: this adapter appears to have no tests + import {deepAccess, generateUUID, isEmpty, isFn, parseSizesInput, parseUrl} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -321,8 +323,7 @@ function createVideoRequestData(bid, bidderRequest) { } function getTopWindowLocation(bidderRequest) { - let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; - return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); + return parseUrl(bidderRequest?.refererInfo?.page || '', { decodeSearchAsString: true }); } function createBannerRequestData(bid, bidderRequest) { diff --git a/modules/saraBidAdapter.md b/modules/saraBidAdapter.md deleted file mode 100644 index 65572528181..00000000000 --- a/modules/saraBidAdapter.md +++ /dev/null @@ -1,40 +0,0 @@ -# Overview - -Module Name: Sara Bidder Adapter -Module Type: Bidder Adapter -Maintainer: github@sara.media - -# Description - -Module that connects to Sara demand source to fetch bids. - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: "sara", - params: { - uid: '5', - priceType: 'gross' // by default is 'net' - } - } - ] - },{ - code: 'test-div', - sizes: [[728, 90]], - bids: [ - { - bidder: "sara", - params: { - uid: 6, - priceType: 'gross' - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/scaleableAnalyticsAdapter.js b/modules/scaleableAnalyticsAdapter.js index d7379462e0d..46f9d45d84d 100644 --- a/modules/scaleableAnalyticsAdapter.js +++ b/modules/scaleableAnalyticsAdapter.js @@ -2,7 +2,7 @@ import { ajax } from '../src/ajax.js'; import CONSTANTS from '../src/constants.json'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { logMessage } from '../src/utils.js'; diff --git a/modules/schain.js b/modules/schain.js index d409e74df48..ef1c6da78ce 100644 --- a/modules/schain.js +++ b/modules/schain.js @@ -19,7 +19,7 @@ _each(MODE, mode => MODES.push(mode)); // validate the supply chain object export function isSchainObjectValid(schainObject, returnOnError) { - let failPrefix = 'Detected something wrong within an schain config:' + let failPrefix = 'Detected something wrong within an schain config:'; let failMsg = ''; function appendFailMsg(msg) { diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index a071cdfea80..03fe99d7a71 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -5,8 +5,10 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { NATIVE } from '../src/mediaTypes.js'; import { _map, deepSetValue, isEmpty, deepAccess } from '../src/utils.js'; import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'seedingAlliance'; +const GVL_ID = 371; const DEFAULT_CUR = 'EUR'; const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; @@ -52,6 +54,8 @@ const NATIVE_PARAMS = { export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, + supportedMediaTypes: [NATIVE], isBidRequestValid: function(bid) { @@ -59,10 +63,13 @@ export const spec = { }, buildRequests: (validBidRequests, bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; const tid = validBidRequests[0].transactionId; const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; - let url = bidderRequest.refererInfo.referer; + let url = bidderRequest.refererInfo.page; const imp = validBidRequests.map((bid, id) => { const assets = _map(bid.nativeParams, (bidParams, key) => { diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 2f61e0bc56a..8c5ab41bd6e 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -147,6 +147,36 @@ function buildBidResponse(seedtagBid) { return bid; } +/** + * + * @returns Measure time to first byte implementation + * @see https://web.dev/ttfb/ + * https://developer.mozilla.org/en-US/docs/Web/API/Navigation_timing_API + */ +function ttfb() { + const ttfb = (() => { + // Timing API V2 + try { + const entry = performance.getEntriesByType('navigation')[0]; + return Math.round(entry.responseStart - entry.startTime); + } catch (e) { + // Timing API V1 + try { + const entry = performance.timing; + return Math.round(entry.responseStart - entry.fetchStart); + } catch (e) { + // Timing API not available + return 0; + } + } + })(); + + // prevent negative or excessive value + // @see https://github.com/googleChrome/web-vitals/issues/162 + // https://github.com/googleChrome/web-vitals/issues/137 + return ttfb >= 0 && ttfb <= performance.now() ? ttfb : 0; +} + export function getTimeoutUrl (data) { let queryParams = ''; if ( @@ -189,13 +219,15 @@ export const spec = { */ buildRequests(validBidRequests, bidderRequest) { const payload = { - url: bidderRequest.refererInfo.referer, + url: bidderRequest.refererInfo.page, publisherToken: validBidRequests[0].params.publisherId, cmp: !!bidderRequest.gdprConsent, timeout: bidderRequest.timeout, version: '$prebid.version$', connectionType: getConnectionType(), - bidRequests: _map(validBidRequests, buildBidRequest) + auctionStart: bidderRequest.auctionStart || Date.now(), + ttfb: ttfb(), + bidRequests: _map(validBidRequests, buildBidRequest), }; if (payload.cmp) { @@ -204,6 +236,10 @@ export const spec = { payload['cd'] = bidderRequest.gdprConsent.consentString; } + if (bidderRequest.uspConsent) { + payload['uspConsent'] = bidderRequest.uspConsent + } + const payloadString = JSON.stringify(payload) return { method: 'POST', diff --git a/modules/seedtagBidAdapter.md b/modules/seedtagBidAdapter.md index f4249fb2e89..061a254c5fa 100644 --- a/modules/seedtagBidAdapter.md +++ b/modules/seedtagBidAdapter.md @@ -8,19 +8,18 @@ Maintainer: prebid@seedtag.com # Description -Module that connects to Seedtag demand sources to fetch bids. +Prebidjs seedtag bidder -# Test Parameters - -## Sample Banner Ad Unit +# Sample integration +## InScreen ```js const adUnits = [ { - code: '/21804003197/prebid_test_300x250', + code: '/21804003197/prebid_test_320x100', mediaTypes: { banner: { - sizes: [[300, 250]] + sizes: [[320, 100]] } }, bids: [ @@ -29,8 +28,7 @@ const adUnits = [ params: { publisherId: '0000-0000-01', // required adUnitId: '0000', // required - placement: 'banner', // required - adPosition: 0 // optional + placement: 'inScreen', // required } } ] @@ -38,55 +36,26 @@ const adUnits = [ ] ``` -## Sample inStream Video Ad Unit - +## InArticle ```js -var adUnits = [{ - code: 'video', - mediaTypes: { - video: { - context: 'instream', // required - playerSize: [600, 300], // required - mimes: ['video/mp4'], // recommended - minduration: 5, // optional - maxduration: 60, // optional - boxingallowed: 1, // optional - skip: 1, // optional - startdelay: 1, // optional - linearity: 1, // optional - battr: [1, 2], // optional - maxbitrate: 10, // optional - playbackmethod: [1], // optional - delivery: [1], // optional - placement: 1, // optional - } - }, - bids: [ - { - bidder: 'seedtag', - params: { - publisherId: '0000-0000-01', // required - adUnitId: '0000', // required - placement: 'video', // required - adPosition: 0, // optional - video: { // optional - context: 'instream', // optional - playerSize: [600, 300], // optional - mimes: ['video/mp4'], // optional - minduration: 5, // optional - maxduration: 60, // optional - boxingallowed: 1, // optional - skip: 1, // optional - startdelay: 1, // optional - linearity: 1, // optional - battr: [1, 2], // optional - maxbitrate: 10, // optional - playbackmethod: [1], // optional - delivery: [1], // optional - placement: 1, // optional +const adUnits = [ + { + code: '/21804003197/prebid_test_300x250', + mediaTypes: { + banner: { + sizes: [[300, 250], [1, 1]] + } + }, + bids: [ + { + bidder: 'seedtag', + params: { + publisherId: '0000-0000-01', // required + adUnitId: '0000', // required + placement: 'inArticle', // required } } - } - ] -}]; + ] + } +] ``` diff --git a/modules/segmentoBidAdapter.md b/modules/segmentoBidAdapter.md deleted file mode 100644 index e64153195c5..00000000000 --- a/modules/segmentoBidAdapter.md +++ /dev/null @@ -1,33 +0,0 @@ -# Overview - -``` -Module Name: Segmento Bidder Adapter -Module Type: Bidder Adapter -Maintainer: ssp@segmento.ru -``` - -# Description - -Module that connects to Segmento's demand sources - -# Test Parameters -``` -var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[240,400],[160,600]], - } - }, - bids: [ - { - bidder: 'segmento', - params: { - placementId: -1 - } - } - ] - } -]; -``` diff --git a/modules/sekindoUMBidAdapter.md b/modules/sekindoUMBidAdapter.md deleted file mode 100644 index eeffff928eb..00000000000 --- a/modules/sekindoUMBidAdapter.md +++ /dev/null @@ -1,43 +0,0 @@ -# Overview - -**Module Name**: sekindoUM Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: nissime@sekindo.com - -# Description - -Connects to Sekindo (part of UM) demand source to fetch bids. -Banner, Outstream and Native formats are supported. - - -# Test Parameters -``` - var adUnits = [{ - code: 'banner-ad-div', - sizes: [[300, 250]], - bids: [{ - bidder: 'sekindoUM', - params: { - spaceId: 14071 - width:300, ///optional - height:250, //optional - } - }] - }, - { - code: 'video-ad-div', - sizes: [[640, 480]], - bids: [{ - bidder: 'sekindoUM', - params: { - spaceId: 87812, - video:{ - playerWidth:640, - playerHeight:480, - vid_vastType: 5 //optional - } - } - }] - } - ]; -``` diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 8ed5699ff22..99a34ae17f4 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -5,13 +5,12 @@ * @requires module:modules/userId */ -import {buildUrl, generateUUID, hasDeviceAccess, logInfo, parseUrl, triggerPixel} from '../src/utils.js'; +import { parseUrl, buildUrl, triggerPixel, logInfo, hasDeviceAccess, generateUUID } from '../src/utils.js'; import {submodule} from '../src/hook.js'; -import {coppaDataHandler} from '../src/adapterManager.js'; +import { coppaDataHandler } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; -const MODULE_TYPE = 'fpid-module'; -export const storage = getStorageManager({moduleName: 'pubCommonId', moduleType: MODULE_TYPE}); +export const storage = getStorageManager({moduleName: 'pubCommonId'}); const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const OPTOUT_NAME = '_pubcid_optout'; @@ -88,7 +87,8 @@ export const sharedIdSystemSubmodule = { return undefined; } logInfo(' Decoded value PubCommonId ' + value); - return {'pubcid': value}; + const idObj = {'pubcid': value}; + return idObj; }, /** * performs action to obtain id diff --git a/modules/sharethroughAnalyticsAdapter.js b/modules/sharethroughAnalyticsAdapter.js index 4f065cbca23..6502c7e3a53 100644 --- a/modules/sharethroughAnalyticsAdapter.js +++ b/modules/sharethroughAnalyticsAdapter.js @@ -1,5 +1,5 @@ import { tryAppendQueryString } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; const emptyUrl = ''; diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 1dd95812e12..cfbd94096bd 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -4,7 +4,7 @@ import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { createEidsArray } from './userId/eids.js'; -const VERSION = '4.1.0'; +const VERSION = '4.2.0'; const BIDDER_CODE = 'sharethrough'; const SUPPLY_ID = 'WYu2BXv1'; @@ -23,7 +23,7 @@ export const sharethroughAdapterSpec = { buildRequests: (bidRequests, bidderRequest) => { const timeout = config.getConfig('bidderTimeout'); - const firstPartyData = config.getConfig('ortb2') || {}; + const firstPartyData = bidderRequest.ortb2 || {}; const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; const secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1); @@ -34,9 +34,9 @@ export const sharethroughAdapterSpec = { cur: ['USD'], tmax: timeout, site: { - domain: window.location.hostname, - page: window.location.href, - ref: deepAccess(bidderRequest, 'refererInfo.referer'), + domain: deepAccess(bidderRequest, 'refererInfo.domain', window.location.hostname), + page: deepAccess(bidderRequest, 'refererInfo.page', window.location.href), + ref: deepAccess(bidderRequest, 'refererInfo.ref'), ...firstPartyData.site, }, device: { @@ -58,14 +58,14 @@ export const sharethroughAdapterSpec = { schain: bidRequests[0].schain, }, }, - bcat: bidRequests[0].params.bcat || [], - badv: bidRequests[0].params.badv || [], + bcat: deepAccess(bidderRequest.ortb2, 'bcat') || bidRequests[0].params.bcat || [], + badv: deepAccess(bidderRequest.ortb2, 'badv') || bidRequests[0].params.badv || [], test: 0, }; req.user = nullish(firstPartyData.user, {}); if (!req.user.ext) req.user.ext = {}; - req.user.ext.eids = userIdAsEids(bidRequests[0]); + req.user.ext.eids = createEidsArray(deepAccess(bidRequests[0], 'userId')) || []; if (bidderRequest.gdprConsent) { const gdprApplies = bidderRequest.gdprConsent.gdprApplies === true; @@ -180,21 +180,12 @@ export const sharethroughAdapterSpec = { }); }, - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - const syncParams = uspConsent ? `&us_privacy=${uspConsent}` : ''; - const syncs = []; - const shouldCookieSync = syncOptions.pixelEnabled && - serverResponses.length > 0 && - serverResponses[0].body && - serverResponses[0].body.cookieSyncUrls; - - if (shouldCookieSync) { - serverResponses[0].body.cookieSyncUrls.forEach(url => { - syncs.push({ type: 'image', url: url + syncParams }); - }); - } + getUserSyncs: (syncOptions, serverResponses) => { + const shouldCookieSync = syncOptions.pixelEnabled && deepAccess(serverResponses, '0.body.cookieSyncUrls') !== undefined; - return syncs; + return shouldCookieSync + ? serverResponses[0].body.cookieSyncUrls.map(url => ({ type: 'image', url: url })) + : []; }, // Empty implementation for prebid core to be able to find it @@ -243,21 +234,6 @@ function getBidRequestFloor(bid) { return floor !== null ? floor : bid.params.floor; } -function userIdAsEids(bidRequest) { - const eids = createEidsArray(deepAccess(bidRequest, 'userId')) || []; - - const flocData = deepAccess(bidRequest, 'userId.flocId'); - const isFlocIdValid = flocData && flocData.id && flocData.version; - if (isFlocIdValid) { - eids.push({ - source: 'chrome.com', - uids: [{ id: flocData.id, atype: 1, ext: { ver: flocData.version } }], - }); - } - - return eids; -} - function getProtocol() { return window.location.protocol; } diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js new file mode 100644 index 00000000000..0ce2eed6479 --- /dev/null +++ b/modules/shinezBidAdapter.js @@ -0,0 +1,435 @@ +import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; + +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const BIDDER_CODE = 'shinez'; +const ADAPTER_VERSION = '1.0.0'; +const TTL = 360; +const CURRENCY = 'USD'; +const SELLER_ENDPOINT = 'https://hb.sweetgum.io/'; +const MODES = { + PRODUCTION: 'hb-sz-multi', + TEST: 'hb-multi-sz-test' +} +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} + +export const spec = { + code: BIDDER_CODE, + version: ADAPTER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function (bidRequest) { + if (!bidRequest.params) { + logWarn('no params have been set to Shinez adapter'); + return false; + } + + if (!bidRequest.params.org) { + logWarn('org is a mandatory param for Shinez adapter'); + return false; + } + + return true; + }, + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; + + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; + + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: getEndpoint(testMode), + data: combinedRequestsObject + } + }, + interpretResponse: function ({body}) { + const bidResponses = []; + + if (body.bids) { + body.bids.forEach(adUnit => { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.requestId, + netRevenue: adUnit.netRevenue || true, + nurl: adUnit.nurl, + mediaType: adUnit.mediaType, + meta: { + mediaType: adUnit.mediaType + } + }; + + if (adUnit.mediaType === VIDEO) { + bidResponse.vastXml = adUnit.vastXml; + } else if (adUnit.mediaType === BANNER) { + bidResponse.ad = adUnit.ad; + } + + if (adUnit.adomain && adUnit.adomain.length) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } + + bidResponses.push(bidResponse); + }); + } + + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + syncs.push({ + type: 'iframe', + url: response.body.params.userSyncURL + }); + } + if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + const pixels = response.body.params.userSyncPixels.map(pixel => { + return { + type: 'image', + url: pixel + } + }) + syncs.push(...pixels) + } + } + return syncs; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } + + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } + } +}; + +registerBidder(spec); + +/** + * Get floor price + * @param bid {bid} + * @returns {Number} + */ +function getFloor(bid, mediaType) { + if (!isFn(bid.getFloor)) { + return 0; + } + let floorResult = bid.getFloor({ + currency: CURRENCY, + mediaType: mediaType, + size: '*' + }); + return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; +} + +/** + * Get the the ad sizes array from the bid + * @param bid {bid} + * @returns {Array} + */ +function getSizesArray(bid, mediaType) { + let sizesArray = [] + + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizesArray = bid.mediaTypes[mediaType].sizes; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizesArray = bid.sizes; + } + + return sizesArray; +} + +/** + * Get schain string value + * @param schainObject {Object} + * @returns {string} + */ +function getSupplyChain(schainObject) { + if (isEmpty(schainObject)) { + return ''; + } + let scStr = `${schainObject.ver},${schainObject.complete}`; + schainObject.nodes.forEach((node) => { + scStr += '!'; + scStr += `${getEncodedValIfNotEmpty(node.asi)},`; + scStr += `${getEncodedValIfNotEmpty(node.sid)},`; + scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; + scStr += `${getEncodedValIfNotEmpty(node.rid)},`; + scStr += `${getEncodedValIfNotEmpty(node.name)},`; + scStr += `${getEncodedValIfNotEmpty(node.domain)}`; + }); + return scStr; +} + +/** + * Get encoded node value + * @param val {string} + * @returns {string} + */ +function getEncodedValIfNotEmpty(val) { + return !isEmpty(val) ? encodeURIComponent(val) : ''; +} + +/** + * Get preferred user-sync method based on publisher configuration + * @param bidderCode {string} + * @returns {string} + */ +function getAllowedSyncMethod(filterSettings, bidderCode) { + const iframeConfigsToCheck = ['all', 'iframe']; + const pixelConfigToCheck = 'image'; + if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return SUPPORTED_SYNC_METHODS.IFRAME; + } + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return SUPPORTED_SYNC_METHODS.PIXEL; + } +} + +/** + * Check if sync rule is supported + * @param syncRule {Object} + * @param bidderCode {string} + * @returns {boolean} + */ +function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; + } + const isInclude = syncRule.filter === 'include'; + const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && contains(bidders, bidderCode); +} + +/** + * Get the seller endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getEndpoint(testMode) { + return testMode + ? SELLER_ENDPOINT + MODES.TEST + : SELLER_ENDPOINT + MODES.PRODUCTION; +} + +/** + * get device type + * @param uad {ua} + * @returns {string} + */ +function getDeviceType(ua) { + if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i + .test(ua.toLowerCase())) { + return '5'; + } + if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i + .test(ua.toLowerCase())) { + return '4'; + } + if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i + .test(ua.toLowerCase())) { + return '3'; + } + return '1'; +} + +function generateBidsParams(validBidRequests, bidderRequest) { + const bidsArray = []; + + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); + } + + return bidsArray; +} + +/** + * Generate bid specific parameters + * @param {bid} bid + * @param {bidderRequest} bidderRequest + * @returns {Object} bid specific params object + */ +function generateBidParameters(bid, bidderRequest) { + const {params} = bid; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); + + // fix floor price in case of NAN + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + transactionId: getBidIdParameter('transactionId', bid), + }; + + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + if (pos) { + bidObject.pos = pos; + } + + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + if (gpid) { + bidObject.gpid = gpid; + } + + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + if (placementId) { + bidObject.placementId = placementId; + } + + if (mediaType === VIDEO) { + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + let playbackMethodValue; + + // verify playbackMethod is of type integer array, or integer only. + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation + playbackMethodValue = playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + playbackMethodValue = playbackMethod; + } + + if (playbackMethodValue) { + bidObject.playbackMethod = playbackMethodValue; + } + + const placement = deepAccess(bid, `mediaTypes.video.placement`); + if (placement) { + bidObject.placement = placement; + } + + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + if (minDuration) { + bidObject.minDuration = minDuration; + } + + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + if (maxDuration) { + bidObject.maxDuration = maxDuration; + } + + const skip = deepAccess(bid, `mediaTypes.video.skip`); + if (skip) { + bidObject.skip = skip; + } + + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + if (linearity) { + bidObject.linearity = linearity; + } + } + + return bidObject; +} + +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; +} + +/** + * Generate params that are common between all bids + * @param {single bid object} generalObject + * @param {bidderRequest} bidderRequest + * @returns {object} the common params object + */ +function generateGeneralParams(generalObject, bidderRequest) { + const domain = window.location.hostname; + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const {bidderCode} = bidderRequest; + const generalBidParams = generalObject.params; + const timeout = config.getConfig('bidderTimeout'); + + // these params are snake_case instead of camelCase to allow backwards compatability on the server. + // in the future, these will be converted to camelCase to match our convention. + const generalParams = { + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: ADAPTER_VERSION, + auction_start: timestamp(), + publisher_id: generalBidParams.org, + publisher_name: domain, + site_domain: domain, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + session_id: getBidIdParameter('auctionId', generalObject), + tmax: timeout + }; + + const userIdsParam = getBidIdParameter('userId', generalObject); + if (userIdsParam) { + generalParams.userIds = JSON.stringify(userIdsParam); + } + + const ortb2Metadata = bidderRequest.ortb2 || {}; + if (ortb2Metadata.site) { + generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); + } + if (ortb2Metadata.user) { + generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); + } + + if (syncEnabled) { + const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); + if (allowedSyncMethod) { + generalParams.cs_method = allowedSyncMethod; + } + } + + if (bidderRequest.uspConsent) { + generalParams.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; + } + + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); + } + + if (bidderRequest.ortb2 && bidderRequest.ortb2.site) { + generalParams.referrer = bidderRequest.ortb2.site.ref; + generalParams.page_url = bidderRequest.ortb2.site.page; + } + + if (bidderRequest && bidderRequest.refererInfo) { + generalParams.referrer = generalParams.referrer || deepAccess(bidderRequest, 'refererInfo.referer'); + generalParams.page_url = generalParams.page_url || config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + } + + return generalParams; +} diff --git a/modules/shinezBidAdapter.md b/modules/shinezBidAdapter.md index e040cfbf36b..f0ef7a6c218 100644 --- a/modules/shinezBidAdapter.md +++ b/modules/shinezBidAdapter.md @@ -1,33 +1,76 @@ -# Overview - -``` -Module Name: Shinez Bidder Adapter -Module Type: Bidder Adapter -Maintainer: tech-team@shinez.io -``` - -# Description - -Connects to shinez.io demand sources. - -The Shinez adapter requires setup and approval from the Shinez team. -Please reach out to tech-team@shinez.io for more information. - -# Test Parameters - -```javascript -var adUnits = [{ - code: "test-div", - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [{ - bidder: "shinez", - params: { - placementId: "00654321" - } - }] -}]; +#Overview + +Module Name: Shinez Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: tech-team@shinez.io + + +# Description + +Module that connects to Shinez's demand sources. + +The Shinez adapter requires setup and approval from the Shinez. Please reach out to tech-team@shinez.io to create an Shinez account. + +The adapter supports Video(instream) & Banner. + +# Bid Parameters +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `org` | required | String | Shinez publisher Id provided by your Shinez representative | "56f91cd4d3e3660002000033" +| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 +| `placementId` | optional | String | A unique placement identifier | "12345678" +| `testMode` | optional | Boolean | This activates the test mode | false + +# Test Parameters +```javascript +var adUnits = [{ + code: 'dfp-video-div', + sizes: [ + [640, 480] + ], + mediaTypes: { + video: { + playerSize: [ + [640, 480] + ], + context: 'instream' + } + }, + bids: [{ + bidder: 'shinez', + params: { + org: '56f91cd4d3e3660002000033', // Required + floorPrice: 2.00, // Optional + placementId: 'shinez-video-test', // Optional + testMode: true // Optional + } + }] + }, + { + code: 'dfp-banner-div', + sizes: [ + [640, 480] + ], + mediaTypes: { + banner: { + sizes: [ + [640, 480] + ] + } + }, + bids: [{ + bidder: 'shinez', + params: { + org: '56f91cd4d3e3660002000033', // Required + floorPrice: 2.00, // Optional + placementId: 'shinez-banner-test', // Optional + testMode: true // Optional + } + }] + } +]; ``` \ No newline at end of file diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index 4c8fb812edc..f9ce3a450cf 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -1,4 +1,11 @@ -import { deepAccess, getBidIdParameter, getWindowTop, logError } from '../src/utils.js'; +import { + deepAccess, + getBidIdParameter, + getWindowTop, + triggerPixel, + logInfo, + logError +} from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -7,6 +14,7 @@ import { loadExternalScript } from '../src/adloader.js'; const PROD_ENDPOINT = 'https://bs.showheroes.com/api/v1/bid'; const STAGE_ENDPOINT = 'https://bid-service.stage.showheroes.com/api/v1/bid'; +const VIRALIZE_ENDPOINT = 'https://ads.viralize.tv/prebid-sh/'; const PROD_PUBLISHER_TAG = 'https://static.showheroes.com/publishertag.js'; const STAGE_PUBLISHER_TAG = 'https://pubtag.stage.showheroes.com/publishertag.js'; const PROD_VL = 'https://video-library.showheroes.com'; @@ -26,12 +34,13 @@ export const spec = { aliases: ['showheroesBs'], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function(bid) { - return !!bid.params.playerId; + return !!bid.params.playerId || !!bid.params.unitId; }, buildRequests: function(validBidRequests, bidderRequest) { let adUnits = []; const pageURL = validBidRequests[0].params.contentPageUrl || bidderRequest.refererInfo.referer; const isStage = !!validBidRequests[0].params.stage; + const isViralize = !!validBidRequests[0].params.unitId; const isOutstream = deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream'; const isCustomRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.customRender'); const isNodeRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.slot') || deepAccess(validBidRequests[0], 'params.outstreamOptions.iframe'); @@ -40,12 +49,19 @@ export const spec = { const isBanner = !!validBidRequests[0].mediaTypes.banner || (isOutstream && !(isCustomRender || isNativeRender || isNodeRender)); const defaultSchain = validBidRequests[0].schain || {}; + const consentData = bidderRequest.gdprConsent || {}; + const gdprConsent = { + apiVersion: consentData.apiVersion || 2, + gdprApplies: consentData.gdprApplies || 0, + consentString: consentData.consentString || '', + } + validBidRequests.forEach((bid) => { const videoSizes = getVideoSizes(bid); const bannerSizes = getBannerSizes(bid); const vpaidMode = getBidIdParameter('vpaidMode', bid.params); - const makeBids = (type, size) => { + const makeBids = (type, size, isViralize) => { let context = ''; let streamType = 2; @@ -61,53 +77,79 @@ export const spec = { } } - const consentData = bidderRequest.gdprConsent || {}; - - const gdprConsent = { - apiVersion: consentData.apiVersion || 2, - gdprApplies: consentData.gdprApplies || 0, - consentString: consentData.consentString || '', - } - - return { + let rBid = { type: streamType, adUnitCode: bid.adUnitCode, bidId: bid.bidId, - mediaType: type, context: context, - playerId: getBidIdParameter('playerId', bid.params), auctionId: bidderRequest.auctionId, bidderCode: BIDDER_CODE, - gdprConsent: gdprConsent, start: +new Date(), timeout: 3000, - size: { - width: size[0], - height: size[1] - }, params: bid.params, - schain: bid.schain || defaultSchain, + schain: bid.schain || defaultSchain }; + + if (isViralize) { + rBid.unitId = getBidIdParameter('unitId', bid.params); + rBid.sizes = size; + rBid.mediaTypes = { + [type]: {'context': context} + }; + } else { + rBid.playerId = getBidIdParameter('playerId', bid.params); + rBid.mediaType = type; + rBid.size = { + width: size[0], + height: size[1] + }; + rBid.gdprConsent = gdprConsent; + } + + return rBid; }; - videoSizes.forEach((size) => { - adUnits.push(makeBids(VIDEO, size)); - }); + if (isViralize) { + if (videoSizes && videoSizes[0]) { + adUnits.push(makeBids(VIDEO, videoSizes, isViralize)); + } + if (bannerSizes && bannerSizes[0]) { + adUnits.push(makeBids(BANNER, bannerSizes, isViralize)); + } + } else { + videoSizes.forEach((size) => { + adUnits.push(makeBids(VIDEO, size)); + }); - bannerSizes.forEach((size) => { - adUnits.push(makeBids(BANNER, size)); - }); + bannerSizes.forEach((size) => { + adUnits.push(makeBids(BANNER, size)); + }); + } }); - return { - url: isStage ? STAGE_ENDPOINT : PROD_ENDPOINT, - method: 'POST', - options: {contentType: 'application/json', accept: 'application/json'}, - data: { + let endpointUrl; + let data; + + const QA = validBidRequests[0].params.qa || {}; + + if (isViralize) { + endpointUrl = VIRALIZE_ENDPOINT; + data = { + 'bidRequests': adUnits, + 'context': { + 'gdprConsent': gdprConsent, + 'schain': defaultSchain, + 'pageURL': QA.pageURL || encodeURIComponent(pageURL) + } + } + } else { + endpointUrl = isStage ? STAGE_ENDPOINT : PROD_ENDPOINT; + + data = { 'user': [], 'meta': { 'adapterVersion': 2, - 'pageURL': encodeURIComponent(pageURL), + 'pageURL': QA.pageURL || encodeURIComponent(pageURL), 'vastCacheEnabled': (!!config.getConfig('cache') && !isBanner && !outstreamOptions) || false, 'isDesktop': getWindowTop().document.documentElement.clientWidth > 700, 'xmlAndTag': !!(isOutstream && isCustomRender) || false, @@ -116,6 +158,13 @@ export const spec = { 'requests': adUnits, 'debug': validBidRequests[0].params.debug || false, } + } + + return { + url: QA.endpoint || endpointUrl, + method: 'POST', + options: {contentType: 'application/json', accept: 'application/json'}, + data: data }; }, interpretResponse: function(response, request) { @@ -149,33 +198,53 @@ export const spec = { } return syncs; }, + + onBidWon(bid) { + if (bid.callbacks) { + triggerPixel(bid.callbacks.won); + } + logInfo( + `Showheroes adapter won the auction. Bid id: ${bid.bidId || bid.requestId}` + ); + }, }; function createBids(bidRes, reqData) { - if (bidRes && (!Array.isArray(bidRes.bids) || bidRes.bids.length < 1)) { + if (!bidRes) { + return []; + } + const responseBids = bidRes.bids || bidRes.bidResponses; + if (!Array.isArray(responseBids) || responseBids.length < 1) { return []; } const bids = []; const bidMap = {}; - (reqData.requests || []).forEach((bid) => { + (reqData.requests || reqData.bidRequests || []).forEach((bid) => { bidMap[bid.bidId] = bid; }); - bidRes.bids.forEach(function (bid) { - const reqBid = bidMap[bid.bidId]; + responseBids.forEach(function (bid) { + const requestId = bid.bidId || bid.requestId; + const reqBid = bidMap[requestId]; const currentBidParams = reqBid.params; + const isViralize = !!reqBid.params.unitId; + const size = { + width: bid.width || bid.size.width, + height: bid.height || bid.size.height + }; + let bidUnit = {}; bidUnit.cpm = bid.cpm; - bidUnit.requestId = bid.bidId; + bidUnit.requestId = requestId; bidUnit.adUnitCode = reqBid.adUnitCode; bidUnit.currency = bid.currency; bidUnit.mediaType = bid.mediaType || VIDEO; bidUnit.ttl = TTL; - bidUnit.creativeId = 'c_' + bid.bidId; + bidUnit.creativeId = 'c_' + requestId; bidUnit.netRevenue = true; - bidUnit.width = bid.size.width; - bidUnit.height = bid.size.height; + bidUnit.width = size.width; + bidUnit.height = size.height; bidUnit.meta = { advertiserDomains: bid.adomain || [] }; @@ -185,24 +254,26 @@ function createBids(bidRes, reqData) { content: bid.vastXml, }; } - if (bid.vastTag) { - bidUnit.vastUrl = bid.vastTag; + if (bid.vastTag || bid.vastUrl) { + bidUnit.vastUrl = bid.vastTag || bid.vastUrl; } if (bid.mediaType === BANNER) { bidUnit.ad = getBannerHtml(bid, reqBid, reqData); } else if (bid.context === 'outstream') { const renderer = Renderer.install({ - id: bid.bidId, + id: requestId, url: 'https://static.showheroes.com/renderer.js', adUnitCode: reqBid.adUnitCode, config: { playerId: reqBid.playerId, - width: bid.size.width, - height: bid.size.height, + width: size.width, + height: size.height, vastUrl: bid.vastTag, vastXml: bid.vastXml, + ad: bid.ad, debug: reqData.debug, - isStage: !!reqData.meta.stage, + isStage: reqData.meta && !!reqData.meta.stage, + isViralize: isViralize, customRender: getBidIdParameter('customRender', currentBidParams.outstreamOptions), slot: getBidIdParameter('slot', currentBidParams.outstreamOptions), iframe: getBidIdParameter('iframe', currentBidParams.outstreamOptions), @@ -218,7 +289,12 @@ function createBids(bidRes, reqData) { } function outstreamRender(bid) { - const embedCode = createOutstreamEmbedCode(bid); + let embedCode; + if (bid.renderer.config.isViralize) { + embedCode = createOutstreamEmbedCodeV2(bid); + } else { + embedCode = createOutstreamEmbedCode(bid); + } if (typeof bid.renderer.config.customRender === 'function') { bid.renderer.config.customRender(bid, embedCode); } else { @@ -238,7 +314,7 @@ function outstreamRender(bid) { logError('[ShowHeroes][renderer] Error: spot not found'); } } catch (err) { - logError('[ShowHeroes][renderer] Error:' + err.message) + logError('[ShowHeroes][renderer] Error:' + err.message); } } } @@ -266,6 +342,12 @@ function createOutstreamEmbedCode(bid) { return fragment; } +function createOutstreamEmbedCodeV2(bid) { + const range = document.createRange(); + range.selectNode(document.getElementsByTagName('body')[0]); + return range.createContextualFragment(getBidIdParameter('ad', bid.renderer.config)); +} + function getBannerHtml (bid, reqBid, reqData) { const isStage = !!reqData.meta.stage; const urls = getEnvURLs(isStage); diff --git a/modules/showheroes-bsBidAdapter.md b/modules/showheroes-bsBidAdapter.md index cde652e9d83..a32a77a2525 100644 --- a/modules/showheroes-bsBidAdapter.md +++ b/modules/showheroes-bsBidAdapter.md @@ -125,3 +125,52 @@ Module that connects to ShowHeroes demand source to fetch bids. } ]; ``` + +# Test Parameters (V2) +``` + var adUnits = [ + { + code: 'video', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + } + }, + bids: [ + { + bidder: "showheroes-bs", + params: { + unitId: 'AACBWAcof-611K4U', + vpaidMode: true // by default is 'false' + } + } + ] + }, + { + code: 'video', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream', + } + }, + bids: [ + { + bidder: "showheroes-bs", + params: { + unitId: 'AACBTwsZVANd9NlB', + + outstreamOptions: { + // Required for the outstream renderer to exact node, one of + iframe: 'iframe_id', + // or + slot: 'slot_id' + } + } + } + ] + } + ]; +``` + diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js index a0521bd5297..dc386813978 100644 --- a/modules/sigmoidAnalyticsAdapter.js +++ b/modules/sigmoidAnalyticsAdapter.js @@ -1,7 +1,7 @@ /* Sigmoid Analytics Adapter for prebid.js v1.1.0-pre Updated : 2018-03-28 */ import {includes} from '../src/polyfill.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 84943450e3b..369276e7638 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -6,7 +6,6 @@ * @module modules/sirdataRtdProvider * @requires module:modules/realTimeData */ -import {getGlobal} from '../src/prebidGlobal.js'; import {deepAccess, deepEqual, deepSetValue, isEmpty, logError, mergeDeep} from '../src/utils.js'; import {submodule} from '../src/hook.js'; import {ajax} from '../src/ajax.js'; @@ -19,7 +18,6 @@ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'SirdataRTDModule'; export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { - const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; moduleConfig.params = moduleConfig.params || {}; var tcString = (userConsent && userConsent.gdpr && userConsent.gdpr.consentString ? userConsent.gdpr.consentString : ''); @@ -37,7 +35,7 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, sendWithCredentials = false; gdprApplies = null; tcString = ''; - } else if (getGlobal().getConfig('consentManagement.gdpr')) { + } else if (config.getConfig('consentManagement.gdpr')) { // Default endpoint is cookieless if gdpr management is set. Needed because the cookie-based endpoint will fail and return error if user is located in Europe and no consent has been given sirdataDomain = 'cookieless-data.com'; sendWithCredentials = false; @@ -49,8 +47,8 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, sirdataDomain = 'sddan.com'; sendWithCredentials = true; } - - var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().referer; + // TODO: is 'page' the right value here? + var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().page; const url = 'https://kvt.' + sirdataDomain + '/api/v1/public/p/' + moduleConfig.params.partnerId + '/d/' + moduleConfig.params.key + '/s?callback=&gdpr=' + gdprApplies + '&gdpr_consent=' + tcString + (actualUrl ? '&url=' + encodeURIComponent(actualUrl) : ''); @@ -61,7 +59,7 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, try { const data = JSON.parse(response); if (data && data.segments) { - addSegmentData(adUnits, data, moduleConfig, onDone); + addSegmentData(reqBidsConfigObj, data, moduleConfig, onDone); } else { onDone(); } @@ -88,10 +86,10 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, }); } -export function setGlobalOrtb2(segments, categories) { +export function setGlobalOrtb2(ortb2, segments, categories) { try { let addOrtb2 = {}; - let testGlobal = getGlobal().getConfig('ortb2') || {}; + let testGlobal = ortb2 || {} if (!deepAccess(testGlobal, 'user.ext.data.sd_rtd') || !deepEqual(testGlobal.user.ext.data.sd_rtd, segments)) { deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); } @@ -99,8 +97,7 @@ export function setGlobalOrtb2(segments, categories) { deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); } if (!isEmpty(addOrtb2)) { - let ortb2 = {ortb2: mergeDeep({}, testGlobal, addOrtb2)}; - getGlobal().setConfig(ortb2); + mergeDeep(ortb2, addOrtb2); } } catch (e) { logError(e) @@ -109,10 +106,10 @@ export function setGlobalOrtb2(segments, categories) { return true; } -export function setBidderOrtb2(bidder, segments, categories) { +export function setBidderOrtb2(bidderOrtb2, bidder, segments, categories) { try { let addOrtb2 = {}; - let testBidder = deepAccess(config.getBidderConfig(), bidder + '.ortb2') || {}; + let testBidder = bidderOrtb2[bidder]; if (!deepAccess(testBidder, 'user.ext.data.sd_rtd') || !deepEqual(testBidder.user.ext.data.sd_rtd, segments)) { deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {}); } @@ -120,8 +117,7 @@ export function setBidderOrtb2(bidder, segments, categories) { deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {}); } if (!isEmpty(addOrtb2)) { - let ortb2 = {ortb2: mergeDeep({}, testBidder, addOrtb2)}; - getGlobal().setBidderConfig({bidders: [bidder], config: ortb2}); + mergeDeep(bidderOrtb2[bidder], addOrtb2) } } catch (e) { logError(e) @@ -172,7 +168,8 @@ export function getSegAndCatsArray(data, minScore) { return sirdataData; } -export function addSegmentData(adUnits, data, moduleConfig, onDone) { +export function addSegmentData(reqBids, data, moduleConfig, onDone) { + const adUnits = reqBids.adUnits; moduleConfig = moduleConfig || {}; moduleConfig.params = moduleConfig.params || {}; const globalMinScore = moduleConfig.params.hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.contextualMinRelevancyScore : 30; @@ -187,7 +184,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { // Global ortb2 if (!biddersParamsExist) { - setGlobalOrtb2(sirdataData.segments, sirdataData.categories); + setGlobalOrtb2(reqBids.ortb2Fragments?.global, sirdataData.segments, sirdataData.categories); } // Google targeting @@ -203,7 +200,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (typeof n.setTargeting !== 'undefined' && sirdataMergedList && sirdataMergedList.length > 0) { n.setTargeting('sd_rtd', sirdataMergedList); } - }) + }); } catch (e) { logError(e); } @@ -227,7 +224,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { curationData = {'segments': [], 'categories': []}; sirdataMergedList = []; - let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore) + let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore); if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { switch (bid.bidder) { @@ -299,13 +296,13 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); } } break; case 'ix': - var ixConfig = getGlobal().getConfig('ix.firstPartyData.sd_rtd'); + var ixConfig = config.getConfig('ix.firstPartyData.sd_rtd'); if (!ixConfig) { // For curation index is pid 27248 curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27248'); @@ -327,7 +324,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { ixLength += entry.toString().length; } }); - getGlobal().setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); + config.setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); } } } @@ -365,7 +362,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); } } break; @@ -381,7 +378,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); } } break; @@ -398,7 +395,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); } } break; @@ -414,7 +411,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); } } break; @@ -430,7 +427,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); } } break; @@ -446,7 +443,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); } } break; @@ -462,7 +459,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); } } break; @@ -479,7 +476,7 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { } } } catch (e) { - logError(e) + logError(e); } }) }); diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 405799813eb..05475b3e143 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -98,7 +98,7 @@ export function checkAdUnitSetupHook(adUnits) { */ if (!isArrayOfNums(config.minViewPort, 2)) { logError(`Ad unit ${adUnitCode}: Invalid declaration of 'minViewPort' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); - isValid = false + isValid = false; return; } /* @@ -129,7 +129,7 @@ export function checkAdUnitSetupHook(adUnits) { Verify that 'config.active' is a 'boolean'. If not, return 'false'. */ - if (mediaType === 'native') { + if (FEATURES.NATIVE && mediaType === 'native') { if (typeof config[propertyName] !== 'boolean') { logError(`Ad unit ${adUnitCode}: Invalid declaration of 'active' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); isValid = false; @@ -206,7 +206,7 @@ export function checkAdUnitSetupHook(adUnits) { } } - if (mediaTypes.native) { + if (FEATURES.NATIVE && mediaTypes.native) { // Apply the old native checks validatedNative = validatedVideo ? adUnitSetupChecks.validateNativeMediaType(validatedVideo) : validatedBanner ? adUnitSetupChecks.validateNativeMediaType(validatedBanner) : adUnitSetupChecks.validateNativeMediaType(adUnit); diff --git a/modules/slimcutBidAdapter.js b/modules/slimcutBidAdapter.js index 537372e687f..a04a2fda89a 100644 --- a/modules/slimcutBidAdapter.js +++ b/modules/slimcutBidAdapter.js @@ -113,8 +113,8 @@ function buildRequestObject(bid) { } function getReferrerInfo(bidderRequest) { let ref = window.location.href; - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - ref = bidderRequest.refererInfo.referer; + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + ref = bidderRequest.refererInfo.page; } return ref; } diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index b792983534d..c783f35d915 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -16,9 +16,10 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { tmax: bidderRequest.timeout, site: { id: window.location.hostname, - domain: window.location.hostname, - page: window.location.href, - ref: bidderRequest.refererInfo.referer + // TODO: do the fallbacks make sense here? + domain: bidderRequest.refererInfo.domain || window.location.hostname, + page: bidderRequest.refererInfo.page || window.location.href, + ref: bidderRequest.refererInfo.ref }, device: { language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', @@ -44,7 +45,7 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { } }; - let ortb2 = config.getConfig('ortb2') || {}; + let ortb2 = bidderRequest.ortb2 || {}; Object.assign(requestTemplate.user, ortb2.user); Object.assign(requestTemplate.site, ortb2.site); @@ -62,7 +63,7 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { if (deepAccess(bidRequest, 'params.app')) { const geo = deepAccess(bidRequest, 'params.app.geo'); deepSetValue(requestTemplate, 'device.geo', geo); - const ifa = deepAccess(bidRequest, 'params.app.ifa') + const ifa = deepAccess(bidRequest, 'params.app.ifa'); deepSetValue(requestTemplate, 'device.ifa', ifa); } @@ -209,7 +210,7 @@ export const spec = { } }; - const videoContext = deepAccess(JSON.parse(bidRequest.data).imp[0], 'video.ext.context') + const videoContext = deepAccess(JSON.parse(bidRequest.data).imp[0], 'video.ext.context'); if (videoContext === ADPOD) { resultingBid.vastXml = bid.adm; resultingBid.mediaType = VIDEO; diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index d6e1c8de452..5d64cad27b5 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -148,7 +148,8 @@ export const spec = { appname: bid.params.appName && bid.params.appName !== '' ? bid.params.appName : undefined, ckid: bid.params.ckId || 0, tagId: bid.adUnitCode, - pageDomain: bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer ? bidderRequest.refererInfo.referer : undefined, + // TODO: is 'page' the right value here? + pageDomain: bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page ? bidderRequest.refererInfo.page : undefined, transactionId: bid.transactionId, timeout: config.getConfig('bidderTimeout'), bidId: bid.bidId, diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index a94ed972b2e..6aab6a8b57e 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -2,6 +2,7 @@ import {deepAccess, isFn, logError, logMessage} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'smarthub'; @@ -100,7 +101,7 @@ function buildRequestParams(bidderRequest = {}, placements = []) { winLocation = window.location; } - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; let refferLocation; try { refferLocation = refferUrl && new URL(refferUrl); @@ -149,6 +150,8 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const tempObj = {}; const len = validBidRequests.length; diff --git a/modules/smartrtbBidAdapter.md b/modules/smartrtbBidAdapter.md deleted file mode 100644 index c44903114cf..00000000000 --- a/modules/smartrtbBidAdapter.md +++ /dev/null @@ -1,41 +0,0 @@ -# Overview - -``` -Module Name: Smart RTB (smrtb.com) Bidder Adapter -Module Type: Bidder Adapter -Maintainer: evanm@smrtb.com -``` - -# Description - -Prebid adapter for Smart RTB. Requires approval and account setup. -Video is supported but requires a publisher supplied adunit renderer at this time. - -# Test Parameters - -## Web -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300,250]] - }, - video: { /* requires publisher supplied renderer */ - context: 'outstream', - playerDimension: [640, 480] - } - }, - bids: [ - { - bidder: "smartrtb", - params: { - zoneId: "N4zTDq3PPEHBIODv7cXK", - forceBid: true - } - } - ] - } - ]; -``` diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 00c962445d9..e5f71265e34 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -67,7 +67,8 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { - const page = bidderRequest.refererInfo.referer; + // TODO: does the fallback make sense here? + const page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; const isPageSecure = !!page.match(/^https:/) const smartxRequests = bidRequests.map(function (bid) { @@ -220,7 +221,7 @@ export const spec = { name: provider, value: targetingstring, } - }) + }); } } @@ -229,7 +230,7 @@ export const spec = { requestPayload.user = { ext: userExt, data: targetingarr - } + }; } return { @@ -302,7 +303,7 @@ export const spec = { const playersize = deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: 'https://dco.smartclip.net/?plc=7777778', + url: 'https://dco.smartclip.net/?plc=7777779', config: { adText: 'SmartX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], @@ -352,65 +353,73 @@ function createOutstreamConfig(bid) { logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); - var smartPlayObj = { + var playerConfig = { minAdWidth: confMinAdWidth, maxAdWidth: confMaxAdWidth, - onStartCallback: function (m, n) { - try { - window.sc_smartIntxtStart(n); - } catch (f) {} - }, - onCappedCallback: function (m, n) { + coreSetup: {}, + layoutSettings: {}, + onCappedCallback: function() { try { - window.sc_smartIntxtNoad(n); - } catch (f) {} - }, - onEndCallback: function (m, n) { - try { - window.sc_smartIntxtEnd(n); + window.sc_smartIntxtNoad(); } catch (f) {} }, }; if (confStartOpen == 'true') { - smartPlayObj.startOpen = true; + playerConfig.startOpen = true; } else if (confStartOpen == 'false') { - smartPlayObj.startOpen = false; + playerConfig.startOpen = false; } if (confEndingScreen == 'true') { - smartPlayObj.endingScreen = true; + playerConfig.endingScreen = true; } else if (confEndingScreen == 'false') { - smartPlayObj.endingScreen = false; + playerConfig.endingScreen = false; } if (confTitle || (typeof bid.renderer.config.outstream_options.title == 'string' && bid.renderer.config.outstream_options.title == '')) { - smartPlayObj.title = confTitle; + playerConfig.layoutSettings.advertisingLabel = confTitle; } if (confSkipOffset) { - smartPlayObj.skipOffset = confSkipOffset; + playerConfig.coreSetup.skipOffset = confSkipOffset; } if (confDesiredBitrate) { - smartPlayObj.desiredBitrate = confDesiredBitrate; + playerConfig.coreSetup.desiredBitrate = confDesiredBitrate; } if (confVisibilityThreshold) { - smartPlayObj.visibilityThreshold = confVisibilityThreshold; + playerConfig.visibilityThreshold = confVisibilityThreshold; } - smartPlayObj.adResponse = bid.vastContent; + playerConfig.adResponse = bid.vastContent; const divID = '[id="' + elementId + '"]'; + var playerListener = function callback(event) { + switch (event) { + case 'AdSlotStarted': + try { + window.sc_smartIntxtStart(); + } catch (f) {} + break; + + case 'AdSlotComplete': + try { + window.sc_smartIntxtEnd(); + } catch (f) {} + break; + } + }; + try { // eslint-disable-next-line - let _outstreamPlayer = new OutstreamPlayer(divID, smartPlayObj); + outstreamplayer.connect(divID).setup(playerConfig, playerListener) } catch (e) { logError('[SMARTX][renderer] Error caught: ' + e); } - return smartPlayObj; + return playerConfig; } /** diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index b999d02b059..e5800e7cad0 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -2,6 +2,7 @@ import { logMessage } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'smartyads'; const AD_URL = 'https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js'; @@ -33,10 +34,14 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + let winTop = window; let location; + // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin try { - location = new URL(bidderRequest.refererInfo.referer) + location = new URL(bidderRequest.refererInfo.page) winTop = window.top; } catch (e) { location = winTop.location; diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index 73bd6101670..f82e7c9258f 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -37,6 +37,11 @@ export const spec = { transactionId: bid.transactionId, timeout: config.getConfig('bidderTimeout'), bidId: bid.bidId, + /** positionType is undocumented + It is unclear what this parameter means. + If it means the same as pos in openRTB, + It should read from openRTB object + or from mediaTypes.banner.pos */ positionType: bid.params.positionType || '', prebidVersion: '$prebid.version$' }; @@ -51,7 +56,7 @@ export const spec = { } if (bidderRequest && bidderRequest.refererInfo) { - payload.pageDomain = bidderRequest.refererInfo.referer || ''; + payload.pageDomain = bidderRequest.refererInfo.page || ''; } if (bidderRequest && bidderRequest.gdprConsent) { diff --git a/modules/smmsBidAdapter.md b/modules/smmsBidAdapter.md deleted file mode 100644 index f1d6e233f96..00000000000 --- a/modules/smmsBidAdapter.md +++ /dev/null @@ -1,60 +0,0 @@ -# Overview - -Module Name: SMMS Bid Adapter - -Maintainer: SBBGRP-SSP-PMP@g.softbank.co.jp - -# Description - -Module that connects to softbank's demand sources - -# Test Parameters - -```javascript - var adUnits = [ - { - code: 'test', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [ - { - bidder: 'smms', - params: { - placementId: 1440837, - currency: 'USD' - } - } - ] - }, - { - code: 'test', - mediaTypes: { - native: { - title: { - required: true, - len: 80 - }, - image: { - required: true, - sizes: [150, 50] - }, - sponsoredBy: { - required: true - } - } - }, - bids: [ - { - bidder: 'smms', - params: { - placementId: 1440838, - currency: 'USD' - } - }, - ], - } - ]; -``` diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js new file mode 100644 index 00000000000..e0ec10c6ed6 --- /dev/null +++ b/modules/snigelBidAdapter.js @@ -0,0 +1,150 @@ +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {deepAccess, isArray, isFn, isPlainObject} from '../src/utils.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; + +const BIDDER_CODE = 'snigel'; +const GVLID = 1076; +const DEFAULT_URL = 'https://adserv.snigelweb.com/bp/v1/prebid'; +const DEFAULT_TTL = 60; +const DEFAULT_CURRENCIES = ['USD']; +const FLOOR_MATCH_ALL_SIZES = '*'; + +const getConfig = config.getConfig; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bidRequest) { + return !!bidRequest.params.placement; + }, + + buildRequests: function (bidRequests, bidderRequest) { + const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); + return { + method: 'POST', + url: getEndpoint(), + data: JSON.stringify({ + id: bidderRequest.bidderRequestId, + cur: getCurrencies(), + test: getTestFlag(), + devw: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, + devh: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight, + version: $$PREBID_GLOBAL$$.version, + gdprApplies: gdprApplies, + gdprConsentString: gdprApplies === true ? deepAccess(bidderRequest, 'gdprConsent.consentString') : undefined, + gdprConsentProv: gdprApplies === true ? deepAccess(bidderRequest, 'gdprConsent.addtlConsent') : undefined, + uspConsent: deepAccess(bidderRequest, 'uspConsent'), + coppa: getConfig('coppa'), + eids: deepAccess(bidRequests, '0.userIdAsEids'), + schain: deepAccess(bidRequests, '0.schain'), + page: getPage(bidderRequest), + placements: bidRequests.map((r) => { + return { + uuid: r.bidId, + name: r.params.placement, + sizes: r.sizes, + floor: getPriceFloor(r, BANNER, FLOOR_MATCH_ALL_SIZES), + }; + }), + }), + bidderRequest, + }; + }, + + interpretResponse: function (serverResponse) { + if (!serverResponse.body || !serverResponse.body.bids) { + return []; + } + + return serverResponse.body.bids.map((bid) => { + return { + requestId: bid.uuid, + cpm: bid.price, + creativeId: bid.crid, + currency: serverResponse.body.cur, + width: bid.width, + height: bid.height, + ad: bid.ad, + netRevenue: true, + ttl: bid.ttl || DEFAULT_TTL, + meta: bid.meta, + }; + }); + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + const syncUrl = getSyncUrl(responses || []); + if (syncUrl && syncOptions.iframeEnabled && hasSyncConsent(gdprConsent, uspConsent)) { + return [{type: 'iframe', url: getSyncEndpoint(syncUrl, gdprConsent)}]; + } + }, +}; + +registerBidder(spec); + +function getPage(bidderRequest) { + return ( + getConfig(`${BIDDER_CODE}.page`) || deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || window.location.href + ); +} + +function getEndpoint() { + return getConfig(`${BIDDER_CODE}.url`) || DEFAULT_URL; +} + +function getTestFlag() { + return getConfig(`${BIDDER_CODE}.test`) === true; +} + +function getCurrencies() { + const currencyOverrides = getConfig(`${BIDDER_CODE}.cur`); + if (currencyOverrides !== undefined && (!isArray(currencyOverrides) || currencyOverrides.length === 0)) { + throw Error('Currency override must be an array with at least one currency'); + } + return currencyOverrides || DEFAULT_CURRENCIES; +} + +function getFloorCurrency() { + return getConfig(`${BIDDER_CODE}.floorCur`) || getCurrencies()[0]; +} + +function getPriceFloor(bidRequest, mediaType, size) { + if (isFn(bidRequest.getFloor)) { + const cur = getFloorCurrency(); + const floorInfo = bidRequest.getFloor({ + currency: cur, + mediaType: mediaType, + size: size, + }); + if (isPlainObject(floorInfo) && !isNaN(floorInfo.floor)) { + return { + cur: floorInfo.currency || cur, + value: floorInfo.floor, + }; + } + } +} + +function hasSyncConsent(gdprConsent, uspConsent) { + if (gdprConsent?.gdprApplies && !hasPurpose1Consent(gdprConsent)) { + return false; + } else if (uspConsent && uspConsent[1] === 'Y' && uspConsent[2] === 'Y') { + return false; + } else { + return true; + } +} + +function getSyncUrl(responses) { + return getConfig(`${BIDDER_CODE}.syncUrl`) || deepAccess(responses[0], 'body.syncUrl'); +} + +function getSyncEndpoint(url, gdprConsent) { + return `${url}?gdpr=${gdprConsent?.gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent( + gdprConsent?.consentString || '' + )}`; +} diff --git a/modules/snigelBidAdapter.md b/modules/snigelBidAdapter.md new file mode 100644 index 00000000000..a83e133144f --- /dev/null +++ b/modules/snigelBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +- Module name: Snigel Bid Adapter +- Module type: Bidder Adapter +- Maintainer: devops@snigel.com +- Bidder code: snigel +- Supported media types: Banner + +# Description + +Connects to Snigel demand sources for bids. + +**Note:** This bid adapter requires our ad operation experts to create an optimized setup for the desired placements on your property. +Please reach out to us [through our contact form](https://snigel.com/get-in-touch). We will reply as soon as possible. + +# Parameters + +| Name | Required | Description | Example | +| :--- | :-------- | :---------- | :------ | +| placement | Yes | Placement identifier | top_leaderboard | + +# Test + +```js +var adUnits = [ + { + code: "example", + mediaTypes: { + banner: { + sizes: [ + [970, 90], + [728, 90], + ], + }, + }, + bids: [ + { + bidder: "snigel", + params: { + placement: "prebid_test_placement", + }, + }, + ], + }, +]; +``` diff --git a/modules/somoBidAdapter.md b/modules/somoBidAdapter.md deleted file mode 100644 index e8457fc0ca2..00000000000 --- a/modules/somoBidAdapter.md +++ /dev/null @@ -1,49 +0,0 @@ -# Overview - -**Module Name**: Somo Audience Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: prebid@somoaudience.com -# Description -Connects to Somo Audience demand source. -Please use ```somo``` as the bidder code. - -For video integration, Somo Audience returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction -# Test Site Parameters -``` - var adUnits = [{ - code: 'banner-ad-div', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [{ - bidder: 'somo', - params: { - placementId: '22a58cfb0c9b656bff713d1236e930e8' - } - }] - }]; -``` -# Test App Parameters -``` -var adUnits = [{ - code: 'banner-ad-div', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [{ - bidder: 'somo', - params: { - placementId: '22a58cfb0c9b656bff713d1236e930e8', - app: { - bundle: 'com.somoaudience.apps', - storeUrl: 'http://somoaudience.com/apps', - domain: 'somoaudience.com', - } - } - }] -}]; -``` diff --git a/modules/sonobiAnalyticsAdapter.js b/modules/sonobiAnalyticsAdapter.js index 0de6647149a..0057944b201 100644 --- a/modules/sonobiAnalyticsAdapter.js +++ b/modules/sonobiAnalyticsAdapter.js @@ -1,5 +1,5 @@ import { deepClone, logInfo, logError } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {ajaxBuilder} from '../src/ajax.js'; @@ -255,7 +255,7 @@ sonobiAdapter.sendData = function (auction, data) { contentType: 'text/plain' } ); -} +}; function _logInfo(message, meta) { logInfo(buildLogMessage(message), meta); diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index c5fc07320d8..3c841cc4d8a 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -11,6 +11,7 @@ const OUTSTREAM_REDNERER_URL = 'https://mtrx.go.sonobi.com/sbi_outstream_rendere export const spec = { code: BIDDER_CODE, + gvlid: 104, supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -56,15 +57,23 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { const bids = validBidRequests.map(bid => { + let mediaType; + + if (deepAccess(bid, 'mediaTypes.video')) { + mediaType = 'video'; + } else if (deepAccess(bid, 'mediaTypes.banner')) { + mediaType = 'display'; + } + let slotIdentifier = _validateSlot(bid); if (/^[\/]?[\d]+[[\/].+[\/]?]?$/.test(slotIdentifier)) { slotIdentifier = slotIdentifier.charAt(0) === '/' ? slotIdentifier : '/' + slotIdentifier; return { - [`${slotIdentifier}|${bid.bidId}`]: `${_validateSize(bid)}${_validateFloor(bid)}${_validateGPID(bid)}` + [`${slotIdentifier}|${bid.bidId}`]: `${_validateSize(bid)}|${_validateFloor(bid)}${_validateGPID(bid)}${_validateMediaType(mediaType)}` } } else if (/^[0-9a-fA-F]{20}$/.test(slotIdentifier) && slotIdentifier.length === 20) { return { - [bid.bidId]: `${slotIdentifier}|${_validateSize(bid)}${_validateFloor(bid)}${_validateGPID(bid)}` + [bid.bidId]: `${slotIdentifier}|${_validateSize(bid)}|${_validateFloor(bid)}${_validateGPID(bid)}${_validateMediaType(mediaType)}` } } else { logError(`The ad unit code or Sonobi Placement id for slot ${bid.bidId} is invalid`); @@ -76,7 +85,8 @@ export const spec = { const payload = { 'key_maker': JSON.stringify(data), - 'ref': bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + 'ref': bidderRequest.refererInfo.page, 's': generateUUID(), 'pv': PAGEVIEW_ID, 'vp': _getPlatform(), @@ -86,7 +96,7 @@ export const spec = { }; - const fpd = config.getConfig('ortb2'); + const fpd = bidderRequest.ortb2; if (fpd) { payload.fpd = JSON.stringify(fpd); @@ -120,7 +130,7 @@ export const spec = { } if (validBidRequests[0].schain) { - payload.schain = JSON.stringify(validBidRequests[0].schain) + payload.schain = JSON.stringify(validBidRequests[0].schain); } if (deepAccess(validBidRequests[0], 'userId') && Object.keys(validBidRequests[0].userId).length > 0) { const userIds = deepClone(validBidRequests[0].userId); @@ -208,7 +218,7 @@ export const spec = { ] = bid.sbi_size.split('x'); let aDomains = []; if (bid.sbi_adomain) { - aDomains = [bid.sbi_adomain] + aDomains = [bid.sbi_adomain]; } const bids = { requestId: bidId, @@ -246,7 +256,7 @@ export const spec = { )); let videoSize = deepAccess(bidRequest, 'params.sizes'); if (Array.isArray(videoSize) && Array.isArray(videoSize[0])) { // handle case of multiple sizes - videoSize = videoSize[0] // Only take the first size for outstream + videoSize = videoSize[0]; // Only take the first size for outstream } if (videoSize) { bids.width = videoSize[0]; @@ -272,7 +282,7 @@ export const spec = { }); }); } - } catch (e) {} + } catch (e) { } return syncs; } }; @@ -285,7 +295,7 @@ function _findBidderRequest(bidderRequests, bidId) { } } -function _validateSize (bid) { +function _validateSize(bid) { if (deepAccess(bid, 'mediaTypes.video')) { return ''; // Video bids arent allowed to override sizes via the trinity request } @@ -303,18 +313,18 @@ function _validateSize (bid) { } } -function _validateSlot (bid) { +function _validateSlot(bid) { if (bid.params.ad_unit) { return bid.params.ad_unit; } return bid.params.placement_id; } -function _validateFloor (bid) { +function _validateFloor(bid) { const floor = getBidFloor(bid); if (floor) { - return `|f=${floor}`; + return `f=${floor},`; } return ''; } @@ -323,11 +333,22 @@ function _validateGPID(bid) { const gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') || deepAccess(getGptSlotInfoForAdUnitCode(bid.adUnitCode), 'gptSlot') || bid.params.ad_unit; if (gpid) { - return `|gpid=${gpid}` + return `gpid=${gpid},` } return '' } +function _validateMediaType(mediaType) { + let mediaTypeValidation = ''; + if (mediaType === 'video') { + mediaTypeValidation = 'c=v,'; + } else if (mediaType === 'display') { + mediaTypeValidation = 'c=d,'; + } + + return mediaTypeValidation; +} + const _creative = (mediaType, referer) => (sbiDc, sbiAid) => { if (mediaType === 'video' || mediaType === 'outstream') { return _videoCreative(sbiDc, sbiAid, referer) @@ -340,7 +361,7 @@ function _videoCreative(sbiDc, sbiAid, referer) { return `https://${sbiDc}apex.go.sonobi.com/vast.xml?vid=${sbiAid}&ref=${encodeURIComponent(referer)}` } -function _getBidIdFromTrinityKey (key) { +function _getBidIdFromTrinityKey(key) { return key.split('|').slice(-1)[0] } diff --git a/modules/sortableAnalyticsAdapter.js b/modules/sortableAnalyticsAdapter.js deleted file mode 100644 index 4580ce6dbb8..00000000000 --- a/modules/sortableAnalyticsAdapter.js +++ /dev/null @@ -1,535 +0,0 @@ -import { logInfo, getParameterByName, getOldestHighestCpmBid } from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; -import adapterManager from '../src/adapterManager.js'; -import {ajax} from '../src/ajax.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import { config } from '../src/config.js'; -import {bidderSettings} from '../src/bidderSettings.js'; - -const DEFAULT_PROTOCOL = 'https'; -const DEFAULT_HOST = 'pa.deployads.com'; -const DEFAULT_URL = `${DEFAULT_PROTOCOL}://${DEFAULT_HOST}/pae`; -const ANALYTICS_TYPE = 'endpoint'; -const UTM_STORE_KEY = 'sortable_utm'; - -export const DEFAULT_PBID_TIMEOUT = 1000; -export const TIMEOUT_FOR_REGISTRY = 250; - -const settings = {}; -const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_ADJUSTMENT, - BID_WON, - BID_TIMEOUT, - } -} = CONSTANTS; - -const minsToMillis = mins => mins * 60 * 1000; -const UTM_TTL = minsToMillis(30); - -const SORTABLE_EVENTS = { - BID_WON: 'pbrw', - BID_TIMEOUT: 'pbto', - ERROR: 'pber', - PB_BID: 'pbid' -}; - -const UTM_PARAMS = [ - 'utm_campaign', - 'utm_source', - 'utm_medium', - 'utm_content', - 'utm_term' -]; - -const EVENT_KEYS_SHORT_NAMES = { - 'auctionId': 'ai', - 'adUnitCode': 'ac', - 'adId': 'adi', - 'bidderAlias': 'bs', - 'bidFactor': 'bif', - 'bidId': 'bid', - 'bidRequestCount': 'brc', - 'bidderRequestId': 'brid', - 'bidRequestedSizes': 'rs', - 'bidTopCpm': 'btcp', - 'bidTopCpmCurrency': 'btcc', - 'bidTopIsNetRevenue': 'btin', - 'bidTopFactor': 'btif', - 'bidTopSrc': 'btsrc', - 'cpm': 'c', - 'currency': 'cc', - 'dealId': 'did', - 'isNetRevenue': 'inr', - 'isTop': 'it', - 'isWinner': 'iw', - 'isTimeout': 'ito', - 'mediaType': 'mt', - 'reachedTop': 'rtp', - 'numIframes': 'nif', - 'size': 'siz', - 'start': 'st', - 'tagId': 'tgid', - 'transactionId': 'trid', - 'ttl': 'ttl', - 'ttr': 'ttr', - 'url': 'u', - 'utm_campaign': 'uc', - 'utm_source': 'us', - 'utm_medium': 'um', - 'utm_content': 'un', - 'utm_term': 'ut' -}; - -const auctionCache = {}; - -let bidderFactors = null; - -let timeoutId = null; -let eventsToBeSent = []; - -function getStorage() { - try { - return window['sessionStorage']; - } catch (e) { - return null; - } -} - -function putParams(k, v) { - try { - const storage = getStorage(); - if (!storage) { - return false; - } - if (v === null) { - storage.removeItem(k); - } else { - storage.setItem(k, JSON.stringify(v)); - } - return true; - } catch (e) { - return false; - } -} - -function getParams(k) { - try { - let storage = getStorage(); - if (!storage) { - return null; - } - let value = storage.getItem(k); - return value === null ? null : JSON.parse(value); - } catch (e) { - return null; - } -} - -function storeParams(key, paramsToSave) { - if (!settings.disableSessionTracking) { - for (let property in paramsToSave) { - if (paramsToSave.hasOwnProperty(property)) { - putParams(key, paramsToSave); - break; - } - } - } -} - -function getSiteKey(options) { - const sortableConfig = config.getConfig('sortable') || {}; - const globalSiteId = sortableConfig.siteId; - return globalSiteId || options.siteId; -} - -function generateRandomId() { - let s = (+new Date()).toString(36); - for (let i = 0; i < 6; ++i) { s += (Math.random() * 36 | 0).toString(36); } - return s; -} - -function getSessionParams() { - const stillValid = paramsFromStorage => (paramsFromStorage.created) < (+new Date() + UTM_TTL); - let sessionParams = null; - if (!settings.disableSessionTracking) { - const paramsFromStorage = getParams(UTM_STORE_KEY); - sessionParams = paramsFromStorage && stillValid(paramsFromStorage) ? paramsFromStorage : null; - } - sessionParams = sessionParams || {'created': +new Date(), 'sessionId': generateRandomId()}; - const urlParams = UTM_PARAMS.map(getParameterByName); - if (UTM_PARAMS.every(key => !sessionParams[key])) { - UTM_PARAMS.forEach((v, i) => sessionParams[v] = urlParams[i] || sessionParams[v]); - sessionParams.created = +new Date(); - storeParams(UTM_STORE_KEY, sessionParams); - } - return sessionParams; -} - -function getPrebidVersion() { - return getGlobal().version; -} - -function getFactor(bidder) { - if (bidder && bidder.bidCpmAdjustment) { - return bidder.bidCpmAdjustment(1.0); - } else { - return null; - } -} - -function getBiddersFactors() { - const result = {}; - const settings = bidderSettings.getSettings(); - if (settings) { - Object.keys(settings).forEach(bidderKey => { - const bidder = settings[bidderKey]; - const factor = getFactor(bidder); - if (factor !== null) { - result[bidderKey] = factor; - } - }); - } - return result; -} - -function getBaseEvent(auctionId, adUnitCode, bidderCode) { - const event = {}; - event.s = settings.key; - event.ai = auctionId; - event.ac = adUnitCode; - event.bs = bidderCode; - return event; -} - -function getBidBaseEvent(auctionId, adUnitCode, bidderCode) { - const sessionParams = getSessionParams(); - const prebidVersion = getPrebidVersion(); - const event = getBaseEvent(auctionId, adUnitCode, bidderCode); - event.sid = sessionParams.sessionId; - event.pv = settings.pageviewId; - event.to = auctionCache[auctionId].timeout; - event.pbv = prebidVersion; - UTM_PARAMS.filter(k => sessionParams[k]).forEach(k => event[EVENT_KEYS_SHORT_NAMES[k]] = sessionParams[k]); - return event; -} - -function createPBBidEvent(bid) { - const event = getBidBaseEvent(bid.auctionId, bid.adUnitCode, bid.bidderAlias); - Object.keys(bid).forEach(k => { - const shortName = EVENT_KEYS_SHORT_NAMES[k]; - if (shortName) { - event[shortName] = bid[k]; - } - }); - event._type = SORTABLE_EVENTS.PB_BID; - return event; -} - -function getBidFactor(bidderAlias) { - if (!bidderFactors) { - bidderFactors = getBiddersFactors(); - } - const factor = bidderFactors[bidderAlias]; - return typeof factor !== 'undefined' ? factor : 1.0; -} - -function createPrebidBidWonEvent({auctionId, adUnitCode, bidderAlias, cpm, currency, isNetRevenue}) { - const bidFactor = getBidFactor(bidderAlias); - const event = getBaseEvent(auctionId, adUnitCode, bidderAlias); - event.bif = bidFactor; - bidderFactors = null; - event.c = cpm; - event.cc = currency; - event.inr = isNetRevenue; - event._type = SORTABLE_EVENTS.BID_WON; - return event; -} - -function createPrebidTimeoutEvent({auctionId, adUnitCode, bidderAlias}) { - const event = getBaseEvent(auctionId, adUnitCode, bidderAlias); - event._type = SORTABLE_EVENTS.BID_TIMEOUT; - return event; -} - -function getDistinct(arr) { - return arr.filter((v, i, a) => a.indexOf(v) === i); -} - -function groupBy(list, keyGetterFn) { - const map = {}; - list.forEach(item => { - const key = keyGetterFn(item); - map[key] = map[key] ? map[key].concat(item) : [item]; - }); - return map; -} - -function mergeAndCompressEventsByType(events, type) { - if (!events.length) { - return {}; - } - const allKeys = getDistinct(events.map(ev => Object.keys(ev)).reduce((prev, curr) => prev.concat(curr), [])); - const eventsAsMap = {}; - allKeys.forEach(k => { - events.forEach(ev => eventsAsMap[k] = eventsAsMap[k] ? eventsAsMap[k].concat(ev[k]) : [ev[k]]); - }); - const allSame = arr => arr.every(el => arr[0] === el); - Object.keys(eventsAsMap) - .forEach(k => eventsAsMap[k] = (eventsAsMap[k].length && allSame(eventsAsMap[k])) ? eventsAsMap[k][0] : eventsAsMap[k]); - eventsAsMap._count = events.length; - const result = {}; - result[type] = eventsAsMap; - return result; -} - -function mergeAndCompressEvents(events) { - const types = getDistinct(events.map(e => e._type)); - const groupedEvents = groupBy(events, e => e._type); - const results = types.map(t => groupedEvents[t]) - .map(events => mergeAndCompressEventsByType(events, events[0]._type)); - return results.reduce((prev, eventMap) => { - const key = Object.keys(eventMap)[0]; - prev[key] = eventMap[key]; - return prev; - }, {}); -} - -function registerEvents(events) { - eventsToBeSent = eventsToBeSent.concat(events); - if (!timeoutId) { - timeoutId = setTimeout(() => { - const _eventsToBeSent = eventsToBeSent.slice(); - eventsToBeSent = []; - sendEvents(_eventsToBeSent); - timeoutId = null; - }, TIMEOUT_FOR_REGISTRY); - } -} - -function sendEvents(events) { - const url = settings.url; - const mergedEvents = mergeAndCompressEvents(events); - const options = { - 'contentType': 'text/plain', - 'method': 'POST', - 'withCredentials': true - }; - const onSend = () => logInfo('Sortable Analytics data sent'); - ajax(url, onSend, JSON.stringify(mergedEvents), options); -} - -// converts [[300, 250], [728, 90]] to '300x250,728x90' -function sizesToString(sizes) { - return sizes.map(s => s.join('x')).join(','); -} - -function dimsToSizeString(width, height) { - return `${width}x${height}`; -} - -function handleBidRequested(event) { - const refererInfo = event.refererInfo; - const url = refererInfo.referer; - const reachedTop = refererInfo.reachedTop; - const numIframes = refererInfo.numIframes; - event.bids.forEach(bid => { - const auctionId = bid.auctionId; - const adUnitCode = bid.adUnitCode; - const tagId = bid.bidder === 'sortable' ? bid.params.tagId : ''; - if (!auctionCache[auctionId].adUnits[adUnitCode]) { - auctionCache[auctionId].adUnits[adUnitCode] = {bids: {}}; - } - const adUnit = auctionCache[auctionId].adUnits[adUnitCode]; - const bids = adUnit.bids; - const newBid = { - adUnitCode: bid.adUnitCode, - auctionId: event.auctionId, - bidderAlias: bid.bidder, - bidId: bid.bidId, - bidderRequestId: bid.bidderRequestId, - bidRequestCount: bid.bidRequestsCount, - bidRequestedSizes: sizesToString(bid.sizes), - currency: bid.currency, - cpm: 0.0, - isTimeout: false, - isTop: false, - isWinner: false, - numIframes: numIframes, - start: event.start, - tagId: tagId, - transactionId: bid.transactionId, - reachedTop: reachedTop, - url: encodeURI(url) - }; - bids[newBid.bidderAlias] = newBid; - }); -} - -function handleBidAdjustment(event) { - const auctionId = event.auctionId; - const adUnitCode = event.adUnitCode; - const adUnit = auctionCache[auctionId].adUnits[adUnitCode]; - const bid = adUnit.bids[event.bidderCode]; - const bidFactor = getBidFactor(event.bidderCode); - bid.adId = event.adId; - bid.adUnitCode = event.adUnitCode; - bid.auctionId = event.auctionId; - bid.bidderAlias = event.bidderCode; - bid.bidFactor = bidFactor; - bid.cpm = event.cpm; - bid.currency = event.currency; - bid.dealId = event.dealId; - bid.isNetRevenue = event.netRevenue; - bid.mediaType = event.mediaType; - bid.responseTimestamp = event.responseTimestamp; - bid.size = dimsToSizeString(event.width, event.height); - bid.ttl = event.ttl; - bid.ttr = event.timeToRespond; -} - -function handleBidWon(event) { - const auctionId = event.auctionId; - const auction = auctionCache[auctionId]; - if (auction) { - const adUnitCode = event.adUnitCode; - const adUnit = auction.adUnits[adUnitCode]; - Object.keys(adUnit.bids).forEach(bidderCode => { - const bidFromUnit = adUnit.bids[bidderCode]; - bidFromUnit.isWinner = event.bidderCode === bidderCode; - }); - } else { - const ev = createPrebidBidWonEvent({ - adUnitCode: event.adUnitCode, - auctionId: event.auctionId, - bidderAlias: event.bidderCode, - currency: event.currency, - cpm: event.cpm, - isNetRevenue: event.netRevenue, - }); - registerEvents([ev]); - } -} - -function handleBidTimeout(event) { - event.forEach(timeout => { - const auctionId = timeout.auctionId; - const adUnitCode = timeout.adUnitCode; - const bidderAlias = timeout.bidder; - const auction = auctionCache[auctionId]; - if (auction) { - const adUnit = auction.adUnits[adUnitCode]; - const bid = adUnit.bids[bidderAlias]; - bid.isTimeout = true; - } else { - const prebidTimeoutEvent = createPrebidTimeoutEvent({auctionId, adUnitCode, bidderAlias}); - registerEvents([prebidTimeoutEvent]); - } - }); -} - -function handleAuctionInit(event) { - const auctionId = event.auctionId; - const timeout = event.timeout; - auctionCache[auctionId] = {timeout: timeout, auctionId: auctionId, adUnits: {}}; -} - -function handleAuctionEnd(event) { - const auction = auctionCache[event.auctionId]; - const adUnits = auction.adUnits; - setTimeout(() => { - const events = Object.keys(adUnits).map(adUnitCode => { - const bidderKeys = Object.keys(auction.adUnits[adUnitCode].bids); - const bids = bidderKeys.map(bidderCode => auction.adUnits[adUnitCode].bids[bidderCode]); - const highestBid = bids.length ? bids.reduce(getOldestHighestCpmBid) : null; - return bidderKeys.map(bidderCode => { - const bid = auction.adUnits[adUnitCode].bids[bidderCode]; - if (highestBid && highestBid.cpm) { - bid.isTop = highestBid.bidderAlias === bid.bidderAlias; - bid.bidTopFactor = getBidFactor(highestBid.bidderAlias); - bid.bidTopCpm = highestBid.cpm; - bid.bidTopCpmCurrency = highestBid.currency; - bid.bidTopIsNetRevenue = highestBid.isNetRevenue; - bid.bidTopSrc = highestBid.bidderAlias; - } - return createPBBidEvent(bid); - }); - }).reduce((prev, curr) => prev.concat(curr), []); - bidderFactors = null; - sendEvents(events); - delete auctionCache[event.auctionId]; - }, settings.timeoutForPbid); -} - -function handleError(eventType, event, e) { - const ev = {}; - ev.s = settings.key; - ev.ti = eventType; - ev.args = JSON.stringify(event); - ev.msg = e.message; - ev._type = SORTABLE_EVENTS.ERROR; - registerEvents([ev]); -} - -const sortableAnalyticsAdapter = Object.assign(adapter({url: DEFAULT_URL, ANALYTICS_TYPE}), { - track({eventType, args}) { - try { - switch (eventType) { - case AUCTION_INIT: - handleAuctionInit(args); - break; - case AUCTION_END: - handleAuctionEnd(args); - break; - case BID_REQUESTED: - handleBidRequested(args); - break; - case BID_ADJUSTMENT: - handleBidAdjustment(args); - break; - case BID_WON: - handleBidWon(args); - break; - case BID_TIMEOUT: - handleBidTimeout(args); - break; - } - } catch (e) { - handleError(eventType, args, e); - } - } -}); - -sortableAnalyticsAdapter.originEnableAnalytics = sortableAnalyticsAdapter.enableAnalytics; - -sortableAnalyticsAdapter.enableAnalytics = function (setupConfig) { - if (this.initConfig(setupConfig)) { - logInfo('Sortable Analytics adapter enabled'); - sortableAnalyticsAdapter.originEnableAnalytics(setupConfig); - } -}; - -sortableAnalyticsAdapter.initConfig = function (setupConfig) { - settings.disableSessionTracking = setupConfig.disableSessionTracking === undefined ? false : setupConfig.disableSessionTracking; - settings.key = getSiteKey(setupConfig.options); - settings.protocol = setupConfig.options.protocol || DEFAULT_PROTOCOL; - settings.url = `${settings.protocol}://${setupConfig.options.eventHost || DEFAULT_HOST}/pae/${settings.key}`; - settings.pageviewId = generateRandomId(); - settings.timeoutForPbid = setupConfig.timeoutForPbid ? Math.max(setupConfig.timeoutForPbid, 0) : DEFAULT_PBID_TIMEOUT; - return !!settings.key; -}; - -sortableAnalyticsAdapter.getOptions = function () { - return settings; -}; - -adapterManager.registerAnalyticsAdapter({ - adapter: sortableAnalyticsAdapter, - code: 'sortable' -}); - -export default sortableAnalyticsAdapter; diff --git a/modules/sortableAnalyticsAdapter.md b/modules/sortableAnalyticsAdapter.md deleted file mode 100644 index a4aa8019031..00000000000 --- a/modules/sortableAnalyticsAdapter.md +++ /dev/null @@ -1,9 +0,0 @@ -# Overview - -Module Name: Sortable Analytics Adapter -Module Type: Analytics Adapter -Maintainer: prebid@sortable.com - -# Description - -Analytics adapter for Sortable. Contact prebid@sortable.com for information. diff --git a/modules/sortableBidAdapter.js b/modules/sortableBidAdapter.js deleted file mode 100644 index 15246a10eab..00000000000 --- a/modules/sortableBidAdapter.js +++ /dev/null @@ -1,366 +0,0 @@ -import { _each, logError, isFn, isPlainObject, isNumber, isStr, deepAccess, parseUrl, _map, getUniqueIdentifierStr, createTrackPixelHtml } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { createEidsArray } from './userId/eids.js'; - -const BIDDER_CODE = 'sortable'; -const SERVER_URL = 'https://c.deployads.com'; - -function setAssetRequired(native, asset) { - if (native.required) { - asset.required = 1; - } - return asset; -} - -function buildNativeRequest(nativeMediaType) { - const assets = []; - const title = nativeMediaType.title; - if (title) { - assets.push(setAssetRequired(title, { - title: {len: title.len} - })); - } - const img = nativeMediaType.image; - if (img) { - assets.push(setAssetRequired(img, { - img: { - type: 3, // Main - wmin: 1, - hmin: 1 - } - })); - } - const icon = nativeMediaType.icon; - if (icon) { - assets.push(setAssetRequired(icon, { - img: { - type: 1, // Icon - wmin: 1, - hmin: 1 - } - })); - } - const body = nativeMediaType.body; - if (body) { - assets.push(setAssetRequired(body, {data: {type: 2}})); - } - const cta = nativeMediaType.cta; - if (cta) { - assets.push(setAssetRequired(cta, {data: {type: 12}})); - } - const sponsoredBy = nativeMediaType.sponsoredBy; - if (sponsoredBy) { - assets.push(setAssetRequired(sponsoredBy, {data: {type: 1}})); - } - - _each(assets, (asset, id) => asset.id = id); - return { - ver: '1', - request: JSON.stringify({ - ver: '1', - assets - }) - }; -} - -function tryParseNativeResponse(adm) { - let native = null; - try { - native = JSON.parse(adm); - } catch (e) { - logError('Sortable bid adapter unable to parse native bid response:\n\n' + e); - } - return native && native.native; -} - -function createImgObject(img) { - if (img.w || img.h) { - return { - url: img.url, - width: img.w, - height: img.h - }; - } else { - return img.url; - } -} - -function interpretNativeResponse(response) { - const native = {}; - if (response.link) { - native.clickUrl = response.link.url; - } - _each(response.assets, asset => { - switch (asset.id) { - case 1: - native.title = asset.title.text; - break; - case 2: - native.image = createImgObject(asset.img); - break; - case 3: - native.icon = createImgObject(asset.img); - break; - case 4: - native.body = asset.data.value; - break; - case 5: - native.cta = asset.data.value; - break; - case 6: - native.sponsoredBy = asset.data.value; - break; - } - }); - return native; -} - -function transformSyncs(responses, type, syncs) { - _each(responses, res => { - if (res.body && res.body.ext && res.body.ext.sync_dsps && res.body.ext.sync_dsps.length) { - _each(res.body.ext.sync_dsps, sync => { - if (sync[0] === type && sync[1]) { - syncs.push({type, url: sync[1]}); - } - }); - } - }); -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return bid.params.floor ? bid.params.floor : null; - } - - // MediaType and Size will automatically get set for us if the bid only has - // one media type or one size. - let floor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; -} - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, NATIVE, VIDEO], - - isBidRequestValid: function(bid) { - const sortableConfig = config.getConfig('sortable'); - const haveSiteId = (sortableConfig && !!sortableConfig.siteId) || bid.params.siteId; - const floor = getBidFloor(bid); - const validFloor = !floor || isNumber(floor); - const validKeywords = !bid.params.keywords || - (isPlainObject(bid.params.keywords) && - Object.keys(bid.params.keywords).every(key => - isStr(key) && isStr(bid.params.keywords[key]) - )) - const isBanner = !bid.mediaTypes || bid.mediaTypes[BANNER] || !(bid.mediaTypes[NATIVE] || bid.mediaTypes[VIDEO]); - const bannerSizes = isBanner ? deepAccess(bid, `mediaType.${BANNER}.sizes`) || bid.sizes : null; - return !!(bid.params.tagId && haveSiteId && validFloor && validKeywords && (!isBanner || - (bannerSizes && bannerSizes.length > 0 && bannerSizes.every(sizeArr => sizeArr.length == 2 && sizeArr.every(num => isNumber(num)))))); - }, - - buildRequests: function(validBidReqs, bidderRequest) { - const sortableConfig = config.getConfig('sortable') || {}; - const globalSiteId = sortableConfig.siteId; - let loc = parseUrl(bidderRequest.refererInfo.referer); - - const sortableImps = _map(validBidReqs, bid => { - const rv = { - id: bid.bidId, - tagid: bid.params.tagId, - ext: {} - }; - const bannerMediaType = deepAccess(bid, `mediaTypes.${BANNER}`); - const nativeMediaType = deepAccess(bid, `mediaTypes.${NATIVE}`); - const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); - if (bannerMediaType || !(nativeMediaType || videoMediaType)) { - const bannerSizes = (bannerMediaType && bannerMediaType.sizes) || bid.sizes; - rv.banner = { - format: _map(bannerSizes, ([width, height]) => ({w: width, h: height})) - }; - } - if (nativeMediaType) { - rv.native = buildNativeRequest(nativeMediaType); - } - if (videoMediaType && videoMediaType.context === 'instream') { - const video = {placement: 1}; - video.mimes = videoMediaType.mimes || []; - video.minduration = deepAccess(bid, 'params.video.minduration') || 10; - video.maxduration = deepAccess(bid, 'params.video.maxduration') || 60; - const startDelay = deepAccess(bid, 'params.video.startdelay'); - if (startDelay != null) { - video.startdelay = startDelay; - } - if (videoMediaType.playerSize && videoMediaType.playerSize.length) { - const size = videoMediaType.playerSize[0]; - video.w = size[0]; - video.h = size[1]; - } - if (videoMediaType.api) { - video.api = videoMediaType.api; - } - if (videoMediaType.protocols) { - video.protocols = videoMediaType.protocols; - } - if (videoMediaType.playbackmethod) { - video.playbackmethod = videoMediaType.playbackmethod; - } - rv.video = video; - } - const floor = getBidFloor(bid); - if (floor) { - rv.floor = floor; - } - if (bid.params.keywords) { - rv.ext.keywords = bid.params.keywords; - } - if (bid.params.bidderParams) { - _each(bid.params.bidderParams, (params, partner) => { - rv.ext[partner] = params; - }); - } - rv.ext.gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - return rv; - }); - const gdprConsent = bidderRequest && bidderRequest.gdprConsent; - const bidUserId = validBidReqs[0].userId; - const eids = createEidsArray(bidUserId); - const sortableBidReq = { - id: getUniqueIdentifierStr(), - imp: sortableImps, - source: { - ext: { - schain: validBidReqs[0].schain - } - }, - regs: { - ext: {} - }, - site: { - domain: loc.hostname, - page: loc.href, - ref: loc.href, - publisher: { - id: globalSiteId || validBidReqs[0].params.siteId, - }, - device: { - w: screen.width, - h: screen.height - }, - }, - user: { - ext: {} - } - }; - if (bidderRequest && bidderRequest.timeout > 0) { - sortableBidReq.tmax = bidderRequest.timeout; - } - if (gdprConsent) { - sortableBidReq.user = { - ext: { - consent: gdprConsent.consentString - } - }; - if (typeof gdprConsent.gdprApplies == 'boolean') { - sortableBidReq.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0 - } - } - if (eids.length) { - sortableBidReq.user.ext.eids = eids; - } - if (bidderRequest.uspConsent) { - sortableBidReq.regs.ext.us_privacy = bidderRequest.uspConsent; - } - return { - method: 'POST', - url: `${SERVER_URL}/openrtb2/auction?src=$$REPO_AND_VERSION$$&host=${loc.hostname}`, - data: JSON.stringify(sortableBidReq), - options: {contentType: 'text/plain'} - }; - }, - - interpretResponse: function(serverResponse) { - const { body: {id, seatbid} } = serverResponse; - const sortableBids = []; - if (id && seatbid) { - _each(seatbid, seatbid => { - _each(seatbid.bid, bid => { - const bidObj = { - requestId: bid.impid, - cpm: parseFloat(bid.price), - width: parseInt(bid.w), - height: parseInt(bid.h), - creativeId: bid.crid || bid.id, - dealId: bid.dealid || null, - currency: 'USD', - netRevenue: true, - mediaType: BANNER, - ttl: 60, - meta: { - advertiserDomains: bid.adomain || [] - } - }; - if (bid.adm) { - const adFormat = deepAccess(bid, 'ext.ad_format') - if (adFormat === 'native') { - let native = tryParseNativeResponse(bid.adm); - if (!native) { - return; - } - bidObj.mediaType = NATIVE; - bidObj.native = interpretNativeResponse(native); - } else if (adFormat === 'instream') { - bidObj.mediaType = VIDEO; - bidObj.vastXml = bid.adm; - } else { - bidObj.mediaType = BANNER; - bidObj.ad = bid.adm; - if (bid.nurl) { - bidObj.ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); - } - } - } else if (bid.nurl) { - bidObj.adUrl = bid.nurl; - } - if (bid.ext) { - bidObj[BIDDER_CODE] = bid.ext; - } - sortableBids.push(bidObj); - }); - }); - } - return sortableBids; - }, - - getUserSyncs: (syncOptions, responses) => { - const syncs = []; - if (syncOptions.iframeEnabled) { - transformSyncs(responses, 'iframe', syncs); - } - if (syncOptions.pixelEnabled) { - transformSyncs(responses, 'image', syncs); - } - return syncs; - }, - - onTimeout(details) { - fetch(`${SERVER_URL}/prebid/timeout`, { - method: 'POST', - body: JSON.stringify(details), - mode: 'no-cors', - headers: new Headers({ - 'Content-Type': 'text/plain' - }) - }); - } -}; - -registerBidder(spec); diff --git a/modules/sortableBidAdapter.md b/modules/sortableBidAdapter.md deleted file mode 100644 index c24ad85b752..00000000000 --- a/modules/sortableBidAdapter.md +++ /dev/null @@ -1,109 +0,0 @@ -# Overview - -``` -Module Name: Sortable Bid Adapter -Module Type: Bidder Adapter -Maintainer: prebid@sortable.com -``` - -# Description - -Sortable's adapter integration to the Prebid library. Posts plain-text JSON to the /openrtb2/auction endpoint. - -# Test Parameters - -``` -var adUnits = [ - { - code: 'test-pb-leaderboard', - mediaTypes: { - banner: { - sizes: [[728, 90]], - } - }, - bids: [{ - bidder: 'sortable', - params: { - tagId: 'test-pb-leaderboard', - siteId: 'prebid.example.com', - 'keywords': { - 'key1': 'val1', - 'key2': 'val2' - } - } - }] - }, { - code: 'test-pb-banner', - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - bids: [{ - bidder: 'sortable', - params: { - tagId: 'test-pb-banner', - siteId: 'prebid.example.com' - } - }] - }, { - code: 'test-pb-sidebar', - mediaTypes: { - banner: { - sizes: [[160, 600]], - } - }, - bids: [{ - bidder: 'sortable', - params: { - tagId: 'test-pb-sidebar', - siteId: 'prebid.example.com', - 'keywords': { - 'keyA': 'valA' - } - } - }] - }, { - code: 'test-pb-native', - mediaTypes: { - native: { - title: { - required: true, - len: 800 - }, - image: { - required: true, - sizes: [790, 294], - }, - sponsoredBy: { - required: true - } - } - }, - bids: [{ - bidder: 'sortable', - params: { - tagId: 'test-pb-native', - siteId: 'prebid.example.com' - } - }] - }, { - code: 'test-pb-video', - mediaTypes: { - video: { - playerSize: [640,480], - context: 'instream' - } - }, - bids: [ - { - bidder: 'sortable', - params: { - tagId: 'test-pb-video', - siteId: 'prebid.example.com' - } - } - ] - } -] -``` diff --git a/modules/sovrnAnalyticsAdapter.js b/modules/sovrnAnalyticsAdapter.js index 065cfaa58bc..a72c4b1a5a5 100644 --- a/modules/sovrnAnalyticsAdapter.js +++ b/modules/sovrnAnalyticsAdapter.js @@ -1,10 +1,11 @@ import {logError, timestamp} from '../src/utils.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adaptermanager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import {ajaxBuilder} from '../src/ajax.js'; import {config} from '../src/config.js'; import {find, includes} from '../src/polyfill.js'; +import {getRefererInfo} from '../src/refererDetection.js'; const ajax = ajaxBuilder(0) @@ -22,49 +23,11 @@ let pbaUrl = 'https://pba.aws.lijit.com/analytics' let currentAuctions = {}; const analyticsType = 'endpoint' -const getClosestTop = () => { - let topFrame = window; - let err = false; - try { - while (topFrame.parent.document !== topFrame.document) { - if (topFrame.parent.document) { - topFrame = topFrame.parent; - } else { - throw new Error(); - } - } - } catch (e) { - // bException = true; - } - - return { - topFrame, - err - }; -}; - -const getBestPageUrl = ({err: crossDomainError, topFrame}) => { - let sBestPageUrl = ''; - - if (!crossDomainError) { - // easy case- we can get top frame location - sBestPageUrl = topFrame.location.href; - } else { - try { - try { - sBestPageUrl = window.top.location.href; - } catch (e) { - let aOrigins = window.location.ancestorOrigins; - sBestPageUrl = aOrigins[aOrigins.length - 1]; - } - } catch (e) { - sBestPageUrl = topFrame.document.referrer; - } - } - - return sBestPageUrl; -}; -const rootURL = getBestPageUrl(getClosestTop()) +const rootURL = (() => { + const ref = getRefererInfo(); + // TODO: does the fallback make sense here? + return ref.page || ref.topmostLocation; +})(); let sovrnAnalyticsAdapter = Object.assign(adapter({url: pbaUrl, analyticsType}), { track({ eventType, args }) { diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index eed9ccb7461..ab8abb7e2b4 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -1,8 +1,21 @@ -import { _each, getBidIdParameter, isArray, deepClone, parseUrl, getUniqueIdentifierStr, deepSetValue, logError, deepAccess, isInteger, logWarn } from '../src/utils.js'; +import { + _each, + getBidIdParameter, + isArray, + getUniqueIdentifierStr, + deepSetValue, + logError, + deepAccess, + isInteger, + logWarn +} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' -import { ADPOD, BANNER, VIDEO } from '../src/mediaTypes.js' -import { createEidsArray } from './userId/eids.js' -import {config} from '../src/config.js' +import { + ADPOD, + BANNER, + VIDEO +} from '../src/mediaTypes.js' +import {createEidsArray} from './userId/eids.js'; const ORTB_VIDEO_PARAMS = { 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), @@ -74,7 +87,6 @@ export const spec = { let iv; let schain; let eids; - let tpid = [] let criteoId; _each(bidReqs, function (bid) { @@ -85,7 +97,6 @@ export const spec = { if (id.source === 'criteo.com') { criteoId = id.uids[0].id } - tpid.push({source: id.source, uid: id.uids[0].id}) } }) } @@ -135,12 +146,11 @@ export const spec = { sovrnImps.push(imp) }) - const fpd = deepClone(config.getConfig('ortb2')) + const fpd = bidderRequest.ortb2 || {}; const site = fpd.site || {} - site.page = bidderRequest.refererInfo.referer - // clever trick to get the domain - site.domain = parseUrl(site.page).hostname + site.page = bidderRequest.refererInfo.page + site.domain = bidderRequest.refererInfo.domain const sovrnBidReq = { id: getUniqueIdentifierStr(), @@ -167,7 +177,6 @@ export const spec = { if (eids) { deepSetValue(sovrnBidReq, 'user.ext.eids', eids) - deepSetValue(sovrnBidReq, 'user.ext.tpid', tpid) if (criteoId) { deepSetValue(sovrnBidReq, 'user.ext.prebid_criteoid', criteoId) } @@ -268,11 +277,16 @@ export const spec = { function _buildVideoRequestObj(bid) { const videoObj = {} + const bidSizes = deepAccess(bid, 'sizes') const videoAdUnitParams = deepAccess(bid, 'mediaTypes.video', {}) const videoBidderParams = deepAccess(bid, 'params.video', {}) const computedParams = {} - if (Array.isArray(videoAdUnitParams.playerSize)) { + if (bidSizes) { + const sizes = (Array.isArray(bidSizes[0])) ? bidSizes[0] : bidSizes + computedParams.w = sizes[0] + computedParams.h = sizes[1] + } else if (Array.isArray(videoAdUnitParams.playerSize)) { const sizes = (Array.isArray(videoAdUnitParams.playerSize[0])) ? videoAdUnitParams.playerSize[0] : videoAdUnitParams.playerSize computedParams.w = sizes[0] computedParams.h = sizes[1] diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 5bea39d95de..31009370d0d 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -1,8 +1,9 @@ -import { logError, deepAccess, isArray, getBidIdParameter, getDNT, deepSetValue, isEmpty, _each, logMessage, logWarn, isBoolean, isNumber, isPlainObject, isFn } from '../src/utils.js'; +import { logError, deepAccess, isArray, getBidIdParameter, getDNT, deepSetValue, isEmpty, _each, logMessage, logWarn, isBoolean, isNumber, isPlainObject, isFn, setScriptAttributes } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; +import { loadExternalScript } from '../src/adloader.js'; const BIDDER_CODE = 'spotx'; const URL = 'https://search.spotxchange.com/openrtb/2.3/dados/'; @@ -68,7 +69,8 @@ export const spec = { * @return {ServerRequest} Info describing the request to the server. */ buildRequests: function(bidRequests, bidderRequest) { - const referer = bidderRequest.refererInfo.referer; + // TODO: does the fallback make sense here? + const referer = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; const isPageSecure = !!referer.match(/^https:/); const siteId = ''; @@ -76,8 +78,6 @@ export const spec = { let page; if (getBidIdParameter('page', bid.params)) { page = getBidIdParameter('page', bid.params); - } else if (config.getConfig('pageUrl')) { - page = config.getConfig('pageUrl'); } else { page = referer; } @@ -194,6 +194,10 @@ export const spec = { if (getBidIdParameter('position', bid.params) != '') { spotxReq.video.ext.pos = getBidIdParameter('position', bid.params); + } else { + if (deepAccess(bid, 'mediaTypes.video.pos')) { + spotxReq.video.ext.pos = deepAccess(bid, 'mediaTypes.video.pos'); + } } if (bid.crumbs && bid.crumbs.pubcid) { @@ -253,18 +257,17 @@ export const spec = { deepSetValue(requestPayload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - // ID5 fied - if (deepAccess(bid, 'userId.id5id.uid')) { - userExt.eids = userExt.eids || []; - userExt.eids.push( - { - source: 'id5-sync.com', - uids: [{ - id: bid.userId.id5id.uid, - ext: bid.userId.id5id.ext || {} - }] + if (bid.userIdAsEids) { + userExt.eids = bid.userIdAsEids; + + userExt.eids.forEach(eid => { + if (eid.source === 'uidapi.com') { + eid.uids.forEach(uid => { + uid.ext = uid.ext || {}; + uid.ext.rtiPartner = 'UID2' + }); } - ) + }); } // Add common id if available @@ -281,26 +284,11 @@ export const spec = { }; } - if (bid && bid.userId && bid.userId.tdid) { - userExt.eids = userExt.eids || []; - userExt.eids.push( - { - source: 'adserver.org', - uids: [{ - id: bid.userId.tdid, - ext: { - rtiPartner: 'TDID' - } - }] - } - ) - } - // Only add the user object if it's not empty if (!isEmpty(userExt)) { requestPayload.user = { ext: userExt }; } - const urlQueryParams = 'src_sys=prebid' + const urlQueryParams = 'src_sys=prebid'; return { method: 'POST', url: URL + channelId + '?' + urlQueryParams, @@ -361,7 +349,7 @@ export const spec = { bid.vastXml = spotxBid.adm; } else { bid.cache_key = spotxBid.ext.cache_key; - bid.vastUrl = 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key + bid.vastUrl = 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key; bid.videoCacheKey = spotxBid.ext.cache_key; } @@ -376,7 +364,7 @@ export const spec = { const playersize = deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); const renderer = Renderer.install({ id: 0, - url: '/', + renderNow: true, config: { adText: 'SpotX Outstream Video Ad via Prebid.js', player_width: playersize[0][0], @@ -418,11 +406,45 @@ export const spec = { } function createOutstreamScript(bid) { - const slot = getBidIdParameter('slot', bid.renderer.config.outstream_options); - logMessage('[SPOTX][renderer] Handle SpotX outstream renderer'); const script = window.document.createElement('script'); + let dataSpotXParams = createScriptAttributeMap(bid); + script.type = 'text/javascript'; script.src = 'https://js.spotx.tv/easi/v1/' + bid.channel_id + '.js'; + + setScriptAttributes(script, dataSpotXParams); + + return script; +} + +function outstreamRender(bid) { + if (bid.renderer.config.outstream_function != null && typeof bid.renderer.config.outstream_function === 'function') { + const script = createOutstreamScript(bid); + bid.renderer.config.outstream_function(bid, script); + } else { + try { + const inIframe = getBidIdParameter('in_iframe', bid.renderer.config.outstream_options); + const easiUrl = 'https://js.spotx.tv/easi/v1/' + bid.channel_id + '.js'; + let attributes = createScriptAttributeMap(bid); + if (inIframe && window.document.getElementById(inIframe).nodeName == 'IFRAME') { + const rawframe = window.document.getElementById(inIframe); + let framedoc = rawframe.contentDocument; + if (!framedoc && rawframe.contentWindow) { + framedoc = rawframe.contentWindow.document; + } + loadExternalScript(easiUrl, BIDDER_CODE, undefined, framedoc, attributes); + } else { + loadExternalScript(easiUrl, BIDDER_CODE, undefined, undefined, attributes); + } + } catch (err) { + logError('[SPOTX][renderer] Error:' + err.message); + } + } +} + +function createScriptAttributeMap(bid) { + const slot = getBidIdParameter('slot', bid.renderer.config.outstream_options); + logMessage('[SPOTX][renderer] Handle SpotX outstream renderer'); let dataSpotXParams = {}; dataSpotXParams['data-spotx_channel_id'] = '' + bid.channel_id; dataSpotXParams['data-spotx_vast_url'] = '' + bid.vastUrl; @@ -437,6 +459,7 @@ function createOutstreamScript(bid) { dataSpotXParams['data-spotx_autoplay'] = '1'; dataSpotXParams['data-spotx_blocked_autoplay_override_mode'] = '1'; dataSpotXParams['data-spotx_video_slot_can_autoplay'] = '1'; + dataSpotXParams['data-spotx_content_container_id'] = slot; const playersizeAutoAdapt = getBidIdParameter('playersize_auto_adapt', bid.renderer.config.outstream_options); if (playersizeAutoAdapt && isBoolean(playersizeAutoAdapt) && playersizeAutoAdapt === true) { @@ -475,42 +498,7 @@ function createOutstreamScript(bid) { } } } - - for (let key in dataSpotXParams) { - if (dataSpotXParams.hasOwnProperty(key)) { - script.setAttribute(key, dataSpotXParams[key]); - } - } - - return script; -} - -function outstreamRender(bid) { - const script = createOutstreamScript(bid); - if (bid.renderer.config.outstream_function != null && typeof bid.renderer.config.outstream_function === 'function') { - bid.renderer.config.outstream_function(bid, script); - } else { - try { - const inIframe = getBidIdParameter('in_iframe', bid.renderer.config.outstream_options); - if (inIframe && window.document.getElementById(inIframe).nodeName == 'IFRAME') { - const rawframe = window.document.getElementById(inIframe); - let framedoc = rawframe.contentDocument; - if (!framedoc && rawframe.contentWindow) { - framedoc = rawframe.contentWindow.document; - } - framedoc.body.appendChild(script); - } else { - const slot = getBidIdParameter('slot', bid.renderer.config.outstream_options); - if (slot && window.document.getElementById(slot)) { - window.document.getElementById(slot).appendChild(script); - } else { - window.document.getElementsByTagName('head')[0].appendChild(script); - } - } - } catch (err) { - logError('[SPOTX][renderer] Error:' + err.message) - } - } + return dataSpotXParams; } registerBidder(spec); diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index 67f806ff792..ffe2bef054c 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -1,9 +1,10 @@ -import { deepAccess, isArray, logWarn, parseUrl, getWindowTop } from '../src/utils.js'; +import { deepAccess, getWindowTop, isArray, logWarn } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { includes as strIncludes } from '../src/polyfill.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'sspBC'; const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; @@ -12,7 +13,7 @@ const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify'; const TRACKER_URL = 'https://bdr.wpcdn.pl/tag/jstracker.js'; const GVLID = 676; const TMAX = 450; -const BIDDER_VERSION = '5.6'; +const BIDDER_VERSION = '5.7'; const DEFAULT_CURRENCY = 'PLN'; const W = window; const { navigator } = W; @@ -99,13 +100,6 @@ const getNotificationPayload = bidData => { } } -const cookieSupport = () => { - const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent); - const useCookies = navigator.cookieEnabled || !!document.cookie.length; - - return !isSafari && useCookies; -}; - const applyClientHints = ortbRequest => { const { location } = document; const { connection = {}, deviceMemory, userAgentData = {} } = navigator; @@ -344,7 +338,7 @@ const mapImpression = slot => { if (!adUnitsCalled[adUnitCode]) { // this is a new adunit - assign & save pbsize adSizesCalled[slotSize] = adSizesCalled[slotSize] ? adSizesCalled[slotSize] += 1 : 1; - adUnitsCalled[adUnitCode] = `${slotSize}_${adSizesCalled[slotSize]}` + adUnitsCalled[adUnitCode] = `${slotSize}_${adSizesCalled[slotSize]}`; } ext.data = Object.assign({ pbsize: adUnitsCalled[adUnitCode] }, ext.data); @@ -537,24 +531,21 @@ const spec = { return true; }, buildRequests(validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + if ((!validBidRequests) || (validBidRequests.length < 1)) { return false; } const siteId = setOnAny(validBidRequests, 'params.siteId'); const publisherId = setOnAny(validBidRequests, 'params.publisherId'); - const page = setOnAny(validBidRequests, 'params.page') || bidderRequest.refererInfo.referer; - const domain = setOnAny(validBidRequests, 'params.domain') || parseUrl(page).hostname; + const page = setOnAny(validBidRequests, 'params.page') || bidderRequest.refererInfo.page; + const domain = setOnAny(validBidRequests, 'params.domain') || bidderRequest.refererInfo.domain; const tmax = setOnAny(validBidRequests, 'params.tmax') ? parseInt(setOnAny(validBidRequests, 'params.tmax'), 10) : TMAX; const pbver = '$prebid.version$'; const testMode = setOnAny(validBidRequests, 'params.test') ? 1 : undefined; - - let ref; - - try { - if (W.self === W.top && document.referrer) { ref = document.referrer; } - } catch (e) { - } + const ref = bidderRequest.refererInfo.ref; const payload = { id: bidderRequest.auctionId, @@ -571,7 +562,11 @@ const spec = { tmax, user: {}, regs: {}, - device: { language: getBrowserLanguage() }, + device: { + language: getBrowserLanguage(), + w: screen.width, + h: screen.height, + }, test: testMode, }; @@ -581,7 +576,7 @@ const spec = { return { method: 'POST', - url: `${BIDDER_URL}?cs=${cookieSupport()}&bdver=${BIDDER_VERSION}&pbver=${pbver}&inver=0`, + url: `${BIDDER_URL}?bdver=${BIDDER_VERSION}&pbver=${pbver}&inver=0`, data: JSON.stringify(payload), bidderRequest, }; @@ -687,7 +682,7 @@ const spec = { site: site.id, slot: site.slot, cpm: bid.cpm.toPrecision(4), - } + }; const jsTracker = '`); - break; - case 'callback': - tracker.value(element); - break; - } -} - -function viewabilityCriteriaMet(observer, vid, element, tracker) { - stopObserving(observer, vid, element); - fireViewabilityTracker(element, tracker); -} - -/** - * Start measuring viewability of an element - * @typedef {{ method: string='img','js','callback', value: string|function }} ViewabilityTracker { method: 'img', value: 'http://my.tracker/123' } - * @typedef {{ inViewThreshold: number, timeInView: number }} ViewabilityCriteria { inViewThreshold: 0.5, timeInView: 1000 } - * @param {string} vid unique viewability identifier - * @param {HTMLElement} element - * @param {ViewabilityTracker} tracker - * @param {ViewabilityCriteria} criteria - */ -export function startMeasurement(vid, element, tracker, criteria) { - if (!isValid(vid, element, tracker, criteria)) { - return; - } - - const options = { - root: null, - rootMargin: '0px', - threshold: criteria.inViewThreshold, - }; - - let observer; - let viewable = false; - let stateChange = (entries) => { - viewable = entries[0].isIntersecting; - - if (viewable) { - observers[vid].timeoutId = window.setTimeout(() => { - viewabilityCriteriaMet(observer, vid, element, tracker); - }, criteria.timeInView); - } else if (observers[vid].timeoutId) { - window.clearTimeout(observers[vid].timeoutId); - } - }; - - observer = new IntersectionObserver(stateChange, options); - observers[vid] = { - observer: observer, - element: element, - timeoutId: null, - done: false, - }; - - observer.observe(element); - - logInfo(`${MODULE_NAME}: startMeasurement called with:`, arguments); -} - -/** - * Stop measuring viewability of an element - * @param {string} vid unique viewability identifier - */ -export function stopMeasurement(vid) { - if (!vid || !observers[vid]) { - logWarn(`${MODULE_NAME}: must provide a registered vid`, vid); - return; - } - - observers[vid].observer.unobserve(observers[vid].element); - if (observers[vid].timeoutId) { - window.clearTimeout(observers[vid].timeoutId); - } - - // allow the observer under this vid to be created again - if (!observers[vid].done) { - delete observers[vid]; - } -} - -function listenMessagesFromCreative() { - window.addEventListener('message', receiveMessage, false); -} - -/** - * Recieve messages from creatives - * @param {MessageEvent} evt - */ -export function receiveMessage(evt) { - var key = evt.message ? 'message' : 'data'; - var data = {}; - try { - data = JSON.parse(evt[key]); - } catch (e) { - return; - } - - if (!data || data.message !== 'Prebid Viewability') { - return; - } - - switch (data.action) { - case 'startMeasurement': - let element = data.elementId && document.getElementById(data.elementId); - if (!element) { - element = find(document.getElementsByTagName('IFRAME'), iframe => (iframe.contentWindow || iframe.contentDocument.defaultView) == evt.source); - } - - startMeasurement(data.vid, element, data.tracker, data.criteria); - break; - case 'stopMeasurement': - stopMeasurement(data.vid); - break; - } + logWarn('Viewability module should not be used. See https://github.com/prebid/Prebid.js/issues/8928'); } init(); diff --git a/modules/viewability.md b/modules/viewability.md deleted file mode 100644 index df93b5c40db..00000000000 --- a/modules/viewability.md +++ /dev/null @@ -1,87 +0,0 @@ -# Overview - -Module Name: Viewability - -Purpose: Track when a given HTML element becomes viewable - -Maintainer: atrajkovic@magnite.com - -# Configuration - -Module does not need any configuration, as long as you include it in your PBJS bundle. -Viewability module has only two functions `startMeasurement` and `stopMeasurement` which can be used to enable more complex viewability measurements. Since it allows tracking from within creative (possibly inside a safe frame) this module registers a message listener, for messages with a format that is described bellow. - -## `startMeasurement` - -| startMeasurement Arg Object | Scope | Type | Description | Example | -| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | -| vid | Required | String | Unique viewability identifier, used to reference particular observer | `"ae0f9"` | -| element | Required | HTMLElement | Reference to an HTML element that needs to be tracked | `document.getElementById('test_div')` | -| tracker | Required | ViewabilityTracker | How viewaility event is communicated back to the parties of interest | `{ method: 'img', value: 'http://my.tracker/123' }` | -| criteria | Required | ViewabilityCriteria| Defines custom viewability criteria using the threshold and duration provided | `{ inViewThreshold: 0.5, timeInView: 1000 }` | - -| ViewabilityTracker | Scope | Type | Description | Example | -| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | -| method | Required | String | Type of method for Tracker | `'img' OR 'js' OR 'callback'` | -| value | Required | String | URL string for 'img' and 'js' Trackers, or a function for 'callback' Tracker | `'http://my.tracker/123'` | - -| ViewabilityCriteria | Scope | Type | Description | Example | -| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | -| inViewThreshold | Required | Number | Represents a percentage threshold for the Element to be registered as in view | `0.5` | -| timeInView | Required | Number | Number of milliseconds that a given element needs to be in view continuously, above the threshold | `1000` | - -## Please Note: -- `vid` allows for multiple trackers, with different criteria to be registered for a given HTML element, independently. It's not autogenerated by `startMeasurement()`, it needs to be provided by the caller so that it doesn't have to be posted back to the source iframe (in case viewability is started from within the creative). -- In case of 'callback' method, HTML element is being passed back to the callback function. -- When a tracker needs to be started, without direct access to pbjs, postMessage mechanism can be used to invoke `startMeasurement`, with a following payload: `vid`, `tracker` and `criteria` as described above, but also with `message: 'Prebid Viewability'` and `action: 'startMeasurement'`. Optionally payload can provide `elementId`, if available at that time (for ad servers where name of the iframe is known, or adservers that render outside an iframe). If `elementId` is not provided, viewability module will try to find the iframe that corresponds to the message source. - - -## `stopMeasurement` - -| stopMeasurement Arg Object | Scope | Type | Description | Example | -| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | -| vid | Required | String | Unique viewability identifier, referencing an already started viewability tracker. | `"ae0f9"` | - -## Please Note: -- When a tracker needs to be stopped, without direct access to pbjs, postMessage mechanism can be used here as well. To invoke `stopMeasurement`, you provide the payload with `vid`, `message: 'Prebid Viewability'` and `action: 'stopMeasurement`. Check the example bellow. - -# Examples - -## Example of starting a viewability measurement, when you have direct access to pbjs -``` -pbjs.viewability.startMeasurement( - 'ae0f9', - document.getElementById('test_div'), - { method: 'img', value: 'http://my.tracker/123' }, - { inViewThreshold: 0.5, timeInView: 1000 } -); -``` - -## Example of starting a viewability measurement from within a rendered creative -``` -let viewabilityRecord = { - vid: 'ae0f9', - tracker: { method: 'img', value: 'http://my.tracker/123'}, - criteria: { inViewThreshold: 0.5, timeInView: 1000 }, - message: 'Prebid Viewability', - action: 'startMeasurement' -} - -window.parent.postMessage(JSON.stringify(viewabilityRecord), '*'); -``` - -## Example of stopping the viewability measurement, when you have direct access to pbjs -``` -pbjs.viewability.stopMeasurement('ae0f9'); -``` - -## Example of stopping the viewability measurement from within a rendered creative -``` -let viewabilityRecord = { - vid: 'ae0f9', - message: 'Prebid Viewability', - action: 'stopMeasurement' -} - -window.parent.postMessage(JSON.stringify(viewabilityRecord), '*'); -``` diff --git a/modules/viewdeosDXBidAdapter.js b/modules/viewdeosDXBidAdapter.js index 9e0cb91af9b..7afd23cbde7 100644 --- a/modules/viewdeosDXBidAdapter.js +++ b/modules/viewdeosDXBidAdapter.js @@ -125,7 +125,8 @@ function parseRTBResponse(serverResponse, bidderRequest) { function bidToTag(bidRequests, bidderRequest) { const tag = { - domain: deepAccess(bidderRequest, 'refererInfo.referer') + // TODO: is 'page' the right value here? + domain: deepAccess(bidderRequest, 'refererInfo.page') }; if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index 696d54e4b52..ee4d9708f15 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -94,8 +94,9 @@ export const spec = { if (bidderRequest) { timeout = bidderRequest.timeout; - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - payload.u = bidderRequest.refererInfo.referer; + if (bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + // TODO: is 'page' the right value here? + payload.u = bidderRequest.refererInfo.page; } if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { @@ -374,7 +375,7 @@ function _isAdSlotExists(adUnitCode) { } const gptAdSlot = getGptSlotInfoForAdUnitCode(adUnitCode); - if (gptAdSlot && gptAdSlot.divId && document.getElementById(gptAdSlot.divId)) { + if (gptAdSlot.divId && document.getElementById(gptAdSlot.divId)) { return true; } diff --git a/modules/vmgBidAdapter.md b/modules/vmgBidAdapter.md deleted file mode 100644 index 3ebfce91dee..00000000000 --- a/modules/vmgBidAdapter.md +++ /dev/null @@ -1,28 +0,0 @@ -# Overview - -``` -Module Name: VMG Bidder Adapter -Module Type: Bidder Adapter -Maintainer: paul@vmgood.com -``` - -# Description - -Connects DFP to the VMG Predict engine. - -# Test Parameters -``` - var adUnits = [{ - code: 'div-0', - mediaTypes: { - banner: { - sizes: sizes - } - }, - bids: [ - { - bidder: 'vmg' - } - ] - }]; -``` diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js index 25dbbda90cf..7b8cb42bb0a 100644 --- a/modules/voxBidAdapter.js +++ b/modules/voxBidAdapter.js @@ -198,7 +198,8 @@ export const spec = { */ buildRequests(validBidRequests, bidderRequest) { const payload = { - url: bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + url: bidderRequest.refererInfo.page, cmp: !!bidderRequest.gdprConsent, bidRequests: buildBidRequests(validBidRequests) }; diff --git a/modules/vrtcalBidAdapter.js b/modules/vrtcalBidAdapter.js index d08ef52106e..36276973c20 100644 --- a/modules/vrtcalBidAdapter.js +++ b/modules/vrtcalBidAdapter.js @@ -2,6 +2,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; import {isFn, isPlainObject} from '../src/utils.js'; +import { config } from '../src/config.js'; export const spec = { code: 'vrtcal', @@ -21,10 +22,32 @@ export const spec = { } } + let gdprApplies = 0; + let gdprConsent = ''; + let ccpa = ''; + let coppa = 0; + let tmax = 0; + + if (bid && bid.gdprConsent) { + gdprApplies = bid.gdprConsent.gdprApplies ? 1 : 0; + gdprConsent = bid.gdprConsent.consentString; + } + + if (bid && bid.uspConsent) { + ccpa = bid.uspConsent; + } + + if (config.getConfig('coppa') === true) { + coppa = 1; + } + + tmax = config.getConfig('bidderTimeout'); + const params = { prebidJS: 1, prebidAdUnitCode: bid.adUnitCode, id: bid.bidId, + tmax: tmax, imp: [{ id: '1', banner: { @@ -41,6 +64,18 @@ export const spec = { device: { ua: 'VRTCAL_FILLED', ip: 'VRTCAL_FILLED' + }, + regs: { + coppa: coppa, + ext: { + gdpr: gdprApplies, + us_privacy: ccpa + } + }, + user: { + ext: { + consent: gdprConsent + } } }; @@ -52,7 +87,7 @@ export const spec = { params.imp[0].banner.h = bid.sizes[0][1]; } - return {method: 'POST', url: 'https://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804', data: JSON.stringify(params), options: {withCredentials: false, crossOrigin: true}} + return {method: 'POST', url: 'https://rtb.vrtcal.com/bidder_prebid.vap?ssp=1804', data: JSON.stringify(params), options: {withCredentials: false, crossOrigin: true}}; }); return requests; diff --git a/modules/vubleAnalyticsAdapter.md b/modules/vubleAnalyticsAdapter.md deleted file mode 100644 index dfe0a8d8eb0..00000000000 --- a/modules/vubleAnalyticsAdapter.md +++ /dev/null @@ -1,23 +0,0 @@ -# Overview - -Module Name: Vuble Analytics Adapter - -Module Type: Vuble Analytics Adapter - -Maintainer: abruyere@mediabong.com - -# Description - -Analytics adapter for vuble.tv Contact contact@mediabong.com for information. - -# Test Parameters - -``` -{ - provider: 'vuble', - options: { - pubId: 18, // require - env: 'net', // require - } -} -``` diff --git a/modules/vubleBidAdapter.md b/modules/vubleBidAdapter.md deleted file mode 100644 index 6bd8d3f779a..00000000000 --- a/modules/vubleBidAdapter.md +++ /dev/null @@ -1,58 +0,0 @@ -# Overview - -``` -Module Name: Vuble Bidder Adapter -Module Type: Vuble Adapter -Maintainer: gv@mediabong.com -``` - -# Description - -Module that connects to Vuble's demand sources - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-video-instream', - sizes: [[640, 360]], - mediaTypes: { - video: { - context: 'instream' - } - }, - bids: [ - { - bidder: "vuble", - params: { - env: 'net', - pubId: '18', - zoneId: '12345', - referrer: "http://www.vuble.tv/", // optional - floorPrice: 5.00 // optional - } - } - ] - }, - { - code: 'test-video-outstream', - sizes: [[640, 360]], - mediaTypes: { - video: { - context: 'outstream' - } - }, - bids: [ - { - bidder: "vuble", - params: { - env: 'net', - pubId: '18', - zoneId: '12345', - referrer: "http://www.vuble.tv/", // optional - floorPrice: 5.00 // optional - } - } - ] - } - ]; \ No newline at end of file diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js index 1a97e3bd351..92b7fc26e4c 100644 --- a/modules/waardexBidAdapter.js +++ b/modules/waardexBidAdapter.js @@ -69,7 +69,8 @@ const getCommonBidsData = bidderRequest => { }; if (bidderRequest && bidderRequest.refererInfo) { - payload.referer = encodeURIComponent(bidderRequest.refererInfo.referer); + // TODO: is 'page' the right value here? + payload.referer = encodeURIComponent(bidderRequest.refererInfo.page || ''); } if (bidderRequest && bidderRequest.uspConsent) { @@ -97,7 +98,7 @@ const getBidRequestToSend = validBidRequest => { bidId: validBidRequest.bidId, bidfloor: 0, position: parseInt(validBidRequest.params.position) || 1, - instl: parseInt(validBidRequest.params.instl) || 0, + instl: deepAccess(validBidRequest.ortb2Imp, 'instl') === 1 || parseInt(validBidRequest.params.instl) === 1 ? 1 : 0, }; if (validBidRequest.mediaTypes[BANNER]) { diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 64cdd6508bb..533e669fcaa 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -86,17 +86,15 @@ import { import { deepSetValue, isEmpty, - mergeDeep, + isFn, logError, - logWarn, - tryAppendQueryString, logMessage, - isFn, isArray, isStr, isBoolean, isPlainObject, deepClone, + tryAppendQueryString, mergeDeep, logWarn } from '../src/utils.js'; import { submodule @@ -201,7 +199,7 @@ function initSubSection(moduleParams, subSection, ...requiredFields) { }); } catch (e) { logError(`unable to initialize: error on ${subSection} configuration: ${e}`); - return false + return false; } logMessage(`weborama ${subSection} initialized with success`); @@ -215,7 +213,7 @@ const globalDefaults = { sendToBidders: true, onData: () => { /* do nothing */ }, -} +}; /** normalize submodule configuration * @param {ModuleParams} moduleParams @@ -560,7 +558,7 @@ function getDataFromLocalStorage(weboDataConf, cacheGet, cacheSet, defaultLocalS } } - const profile = cacheGet() + const profile = cacheGet(); if (profile) { return [profile, false]; @@ -626,7 +624,7 @@ function handleBidRequestData(reqBids, moduleParams) { if (ph.sendToBidders(bid, adUnit.code, cph.data, cph.metadata)) { // logMessage(`handling bidder '${bid.bidder}' with ${ph.metadata.source} data`); - handleBid(bid, cph.data, ph.metadata); + handleBid(reqBids, bid, cph.data, ph.metadata); } }) ) @@ -671,15 +669,17 @@ const SMARTADSERVER = 'smartadserver'; const bidderAliasRegistry = adapterManager.aliasRegistry || {}; /** handle individual bid + * @param reqBids * @param {Object} bid * @param {Object} profile * @param {Object} metadata * @returns {void} */ -function handleBid(bid, profile, metadata) { +function handleBid(reqBids, bid, profile, metadata) { const bidder = bidderAliasRegistry[bid.bidder] || bid.bidder; switch (bidder) { + // TODO: these special cases should not be necessary - all adapters should look into FPD, not just their params case APPNEXUS: handleAppnexusBid(bid, profile); @@ -699,7 +699,7 @@ function handleBid(bid, profile, metadata) { break; default: - handleBidViaORTB2(bid, profile, metadata); + handleBidViaORTB2(reqBids, bid, profile, metadata); } } @@ -786,18 +786,19 @@ function handleRubiconBid(bid, profile, metadata) { } /** handle generic bid via ortb2 arbitrary data + * @param reqBids * @param {Object} bid * @param {Object} profile * @param {Object} metadata * @returns {void} */ -function handleBidViaORTB2(bid, profile, metadata) { +function handleBidViaORTB2(reqBids, bid, profile, metadata) { if (isBoolean(metadata.user)) { logMessage(`bidder '${bid.bidder}' is not directly supported, trying set data via bidder ortb2 fpd`); const section = ((metadata.user) ? 'user' : 'site'); - const base = `ortb2.${section}.ext.data`; + const base = `${bid.bidder}.${section}.ext.data`; - assignProfileToObject(bid, base, profile); + assignProfileToObject(reqBids.ortb2Fragments?.bidder, base, profile); } else { logMessage(`SKIP unsupported bidder '${bid.bidder}', data from '${metadata.source}' is not defined as user or site-centric`); } diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js index ba94f90f9c9..ea6f1bce793 100644 --- a/modules/widespaceBidAdapter.js +++ b/modules/widespaceBidAdapter.js @@ -185,28 +185,6 @@ function storeData(data, name, stringify = true) { function getData(name, remove = true) { let data = []; - if (storage.hasLocalStorage()) { - Object.keys(localStorage).filter((key) => { - if (key.indexOf(name) > -1) { - data.push(storage.getDataFromLocalStorage(key)); - if (remove) { - storage.removeDataFromLocalStorage(key); - } - } - }); - } - - if (storage.cookiesAreEnabled()) { - document.cookie.split(';').forEach((item) => { - let value = item.split('='); - if (value[0].indexOf(name) > -1) { - data.push(value[1]); - if (remove) { - storage.setCookie(value[0], '', 'Thu, 01 Jan 1970 00:00:01 GMT'); - } - } - }); - } return data; } diff --git a/modules/windtalkerBidAdapter.md b/modules/windtalkerBidAdapter.md deleted file mode 100644 index f7441effc47..00000000000 --- a/modules/windtalkerBidAdapter.md +++ /dev/null @@ -1,86 +0,0 @@ -# Overview - -**Module Name**: Windtalker Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: corbin@windtalker.io - -# Description - -Connects to Windtalker demand source to fetch bids. -Banner, Native, Video formats are supported. -Please use ```windtalker``` as the bidder code. - -# Test Parameters -``` - var adUnits = [{ - code: 'dfp-native-div', - mediaTypes: { - native: { - title: { - required: true, - len: 75 - }, - image: { - required: true - }, - body: { - len: 200 - }, - icon: { - required: false - } - } - }, - bids: [{ - bidder: 'windtalker', - params: { - pubId: '584971', - siteId: '584971', - placementId: '123', - bidFloor: '0.001', // optional - ifa: 'XXX-XXX', // optional - latitude: '40.712775', // optional - longitude: '-74.005973', // optional - } - }] - }, - { - code: 'dfp-banner-div', - mediaTypes: { - banner: { - sizes: [ - [300, 250],[300,600] - ], - } - }, - bids: [{ - bidder: 'windtalker', - params: { - pubId: '584971', - siteId: '584971', - placementId: '123', - } - }] - }, - { - code: 'dfp-video-div', - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: "instream" - } - }, - bids: [{ - bidder: 'windtalker', - params: { - pubId: '584971', - siteId: '584971', - placementId: '123', - video: { - skipppable: true, - } - } - }] - } - ]; -``` diff --git a/modules/winrBidAdapter.js b/modules/winrBidAdapter.js index 124aba57866..cd807917944 100644 --- a/modules/winrBidAdapter.js +++ b/modules/winrBidAdapter.js @@ -17,6 +17,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const BIDDER_CODE = 'winr'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -207,7 +208,7 @@ export const spec = { } if (appDeviceObjBid) { - payload.device = appDeviceObj + payload.device = appDeviceObj; } if (appIdObjBid) { payload.app = appIdObj; @@ -227,7 +228,8 @@ export const spec = { if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { - rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + // TODO: this collects everything it finds, except for canonicalUrl + rd_ref: encodeURIComponent(bidderRequest.refererInfo.topmostLocation), rd_top: bidderRequest.refererInfo.reachedTop, rd_ifs: bidderRequest.refererInfo.numIframes, rd_stk: bidderRequest.refererInfo.stack @@ -240,7 +242,6 @@ export const spec = { if (bidRequests[0].userId) { let eids = []; - addUserId(eids, deepAccess(bidRequests[0], `userId.flocId.id`), 'chrome.com', null); addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); @@ -356,24 +357,6 @@ function deleteValues(keyPairObj) { } } -function hasPurpose1Consent(bidderRequest) { - let result = true; - if (bidderRequest && bidderRequest.gdprConsent) { - if ( - bidderRequest.gdprConsent.gdprApplies && - bidderRequest.gdprConsent.apiVersion === 2 - ) { - result = !!( - deepAccess( - bidderRequest.gdprConsent, - 'vendorData.purpose.consents.1' - ) === true - ); - } - } - return result; -} - function formatRequest(payload, bidderRequest) { let request = []; let options = { @@ -382,7 +365,7 @@ function formatRequest(payload, bidderRequest) { let endpointUrl = URL; - if (!hasPurpose1Consent(bidderRequest)) { + if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { endpointUrl = URL_SIMPLE; } diff --git a/modules/xeBidAdapter.js b/modules/xeBidAdapter.js new file mode 100644 index 00000000000..e7a251008bf --- /dev/null +++ b/modules/xeBidAdapter.js @@ -0,0 +1,206 @@ +import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getAdUnitSizes, parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray } from '../src/utils.js'; + +const CUR = 'USD'; +const BIDDER_CODE = 'xe'; +const ENDPOINT = 'https://pbjs.xe.works/bid'; + +/** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ +function isBidRequestValid(req) { + if (req && typeof req.params !== 'object') { + logError('Params is not defined or is incorrect in the bidder settings'); + return false; + } + + if (!getBidIdParameter('env', req.params) || !getBidIdParameter('placement', req.params)) { + logError('Env or placement is not present in bidder params'); + return false; + } + + if (deepAccess(req, 'mediaTypes.video') && !isArray(deepAccess(req, 'mediaTypes.video.playerSize'))) { + logError('mediaTypes.video.playerSize is required for video'); + return false; + } + + return true; +} + +/** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ +function buildRequests(validBidRequests, bidderRequest) { + const { refererInfo = {}, gdprConsent = {}, uspConsent } = bidderRequest; + const requests = validBidRequests.map(req => { + const request = {}; + request.bidId = req.bidId; + request.banner = deepAccess(req, 'mediaTypes.banner'); + request.auctionId = req.auctionId; + request.transactionId = req.transactionId; + request.sizes = parseSizesInput(getAdUnitSizes(req)); + request.schain = req.schain; + request.location = { + page: refererInfo.page, + location: refererInfo.location, + domain: refererInfo.domain, + whost: window.location.host, + ref: refererInfo.ref, + isAmp: refererInfo.isAmp + }; + request.device = { + ua: navigator.userAgent, + lang: navigator.language + }; + request.env = { + env: req.params.env, + placement: req.params.placement + }; + request.ortb2 = req.ortb2; + request.ortb2Imp = req.ortb2Imp; + request.tz = new Date().getTimezoneOffset(); + request.ext = req.params.ext; + request.bc = req.bidRequestsCount; + request.floor = getBidFloor(req); + + if (req.userIdAsEids && req.userIdAsEids.length !== 0) { + request.userEids = req.userIdAsEids; + } else { + request.userEids = []; + } + if (gdprConsent.gdprApplies) { + request.gdprApplies = Number(gdprConsent.gdprApplies); + request.consentString = gdprConsent.consentString; + } else { + request.gdprApplies = 0; + request.consentString = ''; + } + if (uspConsent) { + request.usPrivacy = uspConsent; + } else { + request.usPrivacy = ''; + } + if (config.getConfig('coppa')) { + request.coppa = 1; + } else { + request.coppa = 0; + } + + const video = deepAccess(req, 'mediaTypes.video'); + if (video) { + request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); + request.video = video; + } + + return request; + }); + + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(requests), + withCredentials: true, + bidderRequest, + options: { + contentType: 'application/json' + } + }; +} + +/** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ +function interpretResponse(serverResponse, { bidderRequest }) { + const response = []; + if (!isArray(deepAccess(serverResponse, 'body.data'))) { + return response; + } + + serverResponse.body.data.forEach(serverBid => { + const bid = { + requestId: bidderRequest.bidId, + dealId: bidderRequest.dealId || null, + ...serverBid + }; + response.push(bid); + }); + + return response; +} + +/** +* Register the user sync pixels which should be dropped after the auction. +* +* @param {SyncOptions} syncOptions Which user syncs are allowed? +* @param {ServerResponse[]} serverResponses List of server's responses. +* @return {UserSync[]} The user syncs which should be dropped. +*/ +function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { + const syncs = []; + const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); + + if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { + const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; + const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; + const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; + + pixels.forEach(pixel => { + const [ type, url ] = pixel; + const sync = { type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}` }; + if (type === 'iframe' && syncOptions.iframeEnabled) { + syncs.push(sync) + } else if (type === 'image' && syncOptions.pixelEnabled) { + syncs.push(sync) + } + }); + } + + return syncs; +} + +/** +* Get valid floor value from getFloor fuction. +* +* @param {Object} bid Current bid request. +* @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. +*/ +export function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return null; + } + + let floor = bid.getFloor({ + currency: CUR, + mediaType: '*', + size: '*' + }); + + if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) { + return floor.floor; + } + + return null; +} + +export const spec = { + code: BIDDER_CODE, + aliases: [ 'xeworks', 'lunamediax' ], + supportedMediaTypes: [ BANNER, VIDEO ], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +} + +registerBidder(spec); diff --git a/modules/xeBidAdapter.md b/modules/xeBidAdapter.md new file mode 100644 index 00000000000..aad00cb45e8 --- /dev/null +++ b/modules/xeBidAdapter.md @@ -0,0 +1,54 @@ +# Overview + +``` +Module Name: xe Bidder Adapter +Module Type: xe Bidder Adapter +Maintainer: dima@xe.works +``` + +# Description + +Module that connects to xe.works demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'xe', + params: { + env: 'xe', + placement: 'test-banner', + ext: {} + } + } + ] + }, + { + code: 'test-video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } + }, + bids: [{ + bidder: 'xe', + params: { + env: 'xe', + placement: 'test-video', + ext: {} + } + }] + } +]; +``` \ No newline at end of file diff --git a/modules/xendizBidAdapter.md b/modules/xendizBidAdapter.md deleted file mode 100644 index 4ecabe7070f..00000000000 --- a/modules/xendizBidAdapter.md +++ /dev/null @@ -1,41 +0,0 @@ -# Overview - -Module Name: Xendiz Bidder Adapter -Module Type: Bidder Adapter -Maintainer: hello@xendiz.com - -# Description - -Module that connects to Xendiz demand sources - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: "xendiz", - params: { - pid: '00000000-0000-0000-0000-000000000000' - } - } - ] - },{ - code: 'test-div', - sizes: [[300, 50]], - bids: [ - { - bidder: "xendiz", - params: { - pid: '00000000-0000-0000-0000-000000000000', - ext: { - uid: '550e8400-e29b-41d4-a716-446655440000' - } - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/xhbBidAdapter.md b/modules/xhbBidAdapter.md deleted file mode 100644 index bb95f4f499c..00000000000 --- a/modules/xhbBidAdapter.md +++ /dev/null @@ -1,100 +0,0 @@ -# Overview - -``` -Module Name: XHB Bid Adapter -Module Type: Bidder Adapter -Maintainer: daniel.hoffmann@xaxis.com -``` - -# Description - -Connects to Appnexus exchange for bids. - -XHB bid adapter supports Banner, Video (instream and outstream) and Native. - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - sizes: [[300, 250], [300,600]], - bids: [{ - bidder: 'xhb', - params: { - placementId: '10433394' - } - }] - }, - // Native adUnit - { - code: 'native-div', - sizes: [[300, 250], [300,600]], - mediaTypes: { - native: { - title: { - required: true, - len: 80 - }, - body: { - required: true - }, - image: { - required: true - }, - clickUrl: { - required: true - }, - } - }, - bids: [{ - bidder: 'xhb', - params: { - placementId: '9880618' - } - }] - }, - // Video instream adUnit - { - code: 'video-instream', - sizes: [640, 480], - mediaTypes: { - video: { - context: 'instream' - }, - }, - bids: [{ - bidder: 'xhb', - params: { - placementId: '9333431', - video: { - skippable: true, - playback_methods: ['auto_play_sound_off'] - } - } - }] - }, - // Video outstream adUnit - { - code: 'video-outstream', - sizes: [[640, 480]], - mediaTypes: { - video: { - context: 'outstream' - } - }, - bids: [ - { - bidder: 'xhb', - params: { - placementId: '5768085', - video: { - skippable: true, - playback_method: ['auto_play_sound_off'] - } - } - } - ] - } -]; -``` diff --git a/modules/yahoosspBidAdapter.js b/modules/yahoosspBidAdapter.js index b14efc9bce7..71b1ddca2c4 100644 --- a/modules/yahoosspBidAdapter.js +++ b/modules/yahoosspBidAdapter.js @@ -3,6 +3,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { deepAccess, isFn, isStr, isNumber, isArray, isEmpty, isPlainObject, generateUUID, logInfo, logWarn } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const INTEGRATION_METHOD = 'prebid.js'; const BIDDER_CODE = 'yahoossp'; @@ -54,14 +55,6 @@ const SUPPORTED_USER_ID_SOURCES = [ ]; /* Utility functions */ -function hasPurpose1Consent(bidderRequest) { - if (bidderRequest && bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { - return deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true; - } - } - return true; -} function getSize(size) { return { @@ -239,7 +232,7 @@ function generateOpenRtbObject(bidderRequest, bid) { cur: [getFloorModuleData(bidderRequest).currency || deepAccess(bid, 'params.bidOverride.cur') || DEFAULT_CURRENCY], imp: [], site: { - page: deepAccess(bidderRequest, 'refererInfo.referer'), + page: deepAccess(bidderRequest, 'refererInfo.page'), }, device: { dnt: 0, @@ -284,11 +277,12 @@ function generateOpenRtbObject(bidderRequest, bid) { outBoundBidRequest.site.id = bid.params.dcn; }; - if (config.getConfig('ortb2')) { + if (bidderRequest.ortb2) { outBoundBidRequest = appendFirstPartyData(outBoundBidRequest, bid); }; - if (deepAccess(bid, 'schain')) { + const schainData = deepAccess(bid, 'schain.nodes'); + if (isArray(schainData) && schainData.length > 0) { outBoundBidRequest.source.ext.schain = bid.schain; outBoundBidRequest.source.ext.schain.nodes[0].rid = outBoundBidRequest.id; }; @@ -376,7 +370,7 @@ function appendImpObject(bid, openRtbObject) { }; function appendFirstPartyData(outBoundBidRequest, bid) { - const ortb2Object = config.getConfig('ortb2'); + const ortb2Object = bid.ortb2; const siteObject = deepAccess(ortb2Object, 'site') || undefined; const siteContentObject = deepAccess(siteObject, 'content') || undefined; const siteContentDataArray = deepAccess(siteObject, 'content.data') || undefined; @@ -414,7 +408,7 @@ function appendFirstPartyData(outBoundBidRequest, bid) { newDataObject = validateAppendObject('object', allowedContentDataObjectKeys, dataObject, newDataObject); outBoundBidRequest.site.content.data = []; outBoundBidRequest.site.content.data.push(newDataObject); - }) + }); }; }; @@ -434,7 +428,7 @@ function appendFirstPartyData(outBoundBidRequest, bid) { } }; outBoundBidRequest.app.content.data.push(newDataObject); - }) + }); }; }; @@ -494,7 +488,7 @@ function generateServerRequest({payload, requestOptions, bidderRequest}) { function createRenderer(bidderRequest, bidResponse) { const renderer = Renderer.install({ - url: 'https://cdn.vidible.tv/prod/hb-outstream-renderer/renderer.js', + url: 'https://s.yimg.com/kp/prebid-outstream-renderer/renderer.js', loaded: false, adUnitCode: bidderRequest.adUnitCode }) @@ -547,7 +541,7 @@ export const spec = { } }; - requestOptions.withCredentials = hasPurpose1Consent(bidderRequest); + requestOptions.withCredentials = hasPurpose1Consent(bidderRequest.gdprConsent); const filteredBidRequests = filterBidRequestByMode(validBidRequests); diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index e20f71bc08d..7da2872a3a6 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -1,4 +1,4 @@ -import {parseUrl, formatQS, deepAccess} from '../src/utils.js'; +import {formatQS, deepAccess} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'yandex'; @@ -20,8 +20,8 @@ export const spec = { let referrer = ''; if (bidderRequest && bidderRequest.refererInfo) { - const url = parseUrl(bidderRequest.refererInfo.referer); - referrer = url.hostname; + // TODO: is 'domain' the right value here? + referrer = bidderRequest.refererInfo.domain } return validBidRequests.map((bidRequest) => { @@ -65,7 +65,7 @@ export const spec = { withCredentials, }, bidRequest, - } + }; }); }, diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 9535461f4c7..e5e452b4ce0 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -1,9 +1,9 @@ -import { _each, deepAccess, isArray, isPlainObject, timestamp } from '../src/utils.js' +import { _each, deepAccess, isArray, isFn, isPlainObject, timestamp } from '../src/utils.js' import { registerBidder } from '../src/adapters/bidderFactory.js' import { find } from '../src/polyfill.js' import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js' import { Renderer } from '../src/Renderer.js' -import { config } from '../src/config.js' +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const ENDPOINT = 'https://ad.yieldlab.net' const BIDDER_CODE = 'yieldlab' @@ -11,12 +11,17 @@ const BID_RESPONSE_TTL_SEC = 300 const CURRENCY_CODE = 'EUR' const OUTSTREAMPLAYER_URL = 'https://ad.adition.com/dynamic.ad?a=o193092&ma_loadEvent=ma-start-event' const GVLID = 70 +const DIMENSION_SIGN = 'x' export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [VIDEO, BANNER, NATIVE], + /** + * @param {object} bid + * @returns {boolean} + */ isBidRequestValid: function (bid) { if (bid && bid.params && bid.params.adslotId && bid.params.supplyId) { return true @@ -26,11 +31,16 @@ export const spec = { /** * This method should build correct URL - * @param validBidRequests - * @returns {{method: string, url: string}} + * @param {BidRequest[]} validBidRequests + * @param [bidderRequest] + * @returns {ServerRequest|ServerRequest[]} */ buildRequests: function (validBidRequests, bidderRequest) { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + const adslotIds = [] + const adslotSizes = []; const timestamp = Date.now() const query = { ts: timestamp, @@ -39,6 +49,13 @@ export const spec = { _each(validBidRequests, function (bid) { adslotIds.push(bid.params.adslotId) + const sizes = extractSizes(bid) + if (sizes.length > 0) { + adslotSizes.push(bid.params.adslotId + ':' + sizes.join('|')) + } + if (bid.params.extId) { + query.id = bid.params.extId; + } if (bid.params.targeting) { query.t = createTargetingString(bid.params.targeting) } @@ -46,7 +63,7 @@ export const spec = { query.ids = createUserIdString(bid.userIdAsEids) } if (bid.params.customParams && isPlainObject(bid.params.customParams)) { - for (let prop in bid.params.customParams) { + for (const prop in bid.params.customParams) { query[prop] = bid.params.customParams[prop] } } @@ -58,11 +75,16 @@ export const spec = { if (iabContent) { query.iab_content = createIabContentString(iabContent) } + const floor = getBidFloor(bid, sizes) + if (floor) { + query.floor = floor; + } }) if (bidderRequest) { - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - query.pubref = bidderRequest.refererInfo.referer + if (bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + // TODO: is 'page' the right value here? + query.pubref = bidderRequest.refererInfo.page } if (bidderRequest.gdprConsent) { @@ -74,6 +96,9 @@ export const spec = { } const adslots = adslotIds.join(',') + if (adslotSizes.length > 0) { + query.sizes = adslotSizes.join(',') + } const queryString = createQueryString(query) return { @@ -86,8 +111,9 @@ export const spec = { /** * Map ad values and pricing and stuff - * @param serverResponse - * @param originalBidRequest + * @param {ServerResponse} serverResponse + * @param {BidRequest} originalBidRequest + * @returns {Bid[]} */ interpretResponse: function (serverResponse, originalBidRequest) { const bidResponses = [] @@ -99,7 +125,7 @@ export const spec = { return } - let matchedBid = find(serverResponse.body, function (bidResponse) { + const matchedBid = find(serverResponse.body, function (bidResponse) { return bidRequest.params.adslotId == bidResponse.id }) @@ -150,11 +176,12 @@ export const spec = { } if (isNative(bidRequest, adType)) { + // there may be publishers still rely on it const url = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}` bidResponse.adUrl = url bidResponse.mediaType = NATIVE const nativeImageAssetObj = find(matchedBid.native.assets, e => e.id === 2) - const nativeImageAsset = nativeImageAssetObj ? nativeImageAssetObj.img : {url: '', w: 0, h: 0}; + const nativeImageAsset = nativeImageAssetObj ? nativeImageAssetObj.img : { url: '', w: 0, h: 0 }; const nativeTitleAsset = find(matchedBid.native.assets, e => e.id === 1) const nativeBodyAsset = find(matchedBid.native.assets, e => e.id === 3) bidResponse.native = { @@ -189,7 +216,7 @@ export const spec = { const syncs = []; if (syncOptions.iframeEnabled) { - let params = []; + const params = []; params.push(`ts=${timestamp()}`); params.push(`type=h`) if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { @@ -234,7 +261,7 @@ function isNative(format, adtype) { * @returns {Boolean} */ function isOutstream(format) { - let context = deepAccess(format, 'mediaTypes.video.context') + const context = deepAccess(format, 'mediaTypes.video.context') return (context === 'outstream') } @@ -244,7 +271,7 @@ function isOutstream(format) { * @returns {Array} */ function getPlayerSize(format) { - let playerSize = deepAccess(format, 'mediaTypes.video.playerSize') + const playerSize = deepAccess(format, 'mediaTypes.video.playerSize') return (playerSize && isArray(playerSize[0])) ? playerSize[0] : playerSize } @@ -254,7 +281,7 @@ function getPlayerSize(format) { * @returns {Array} */ function parseSize(size) { - return size.split('x').map(Number) + return size.split(DIMENSION_SIGN).map(Number) } /** @@ -263,7 +290,7 @@ function parseSize(size) { * @returns {String} */ function createUserIdString(eids) { - let str = [] + const str = [] for (let i = 0; i < eids.length; i++) { str.push(eids[i].source + ':' + eids[i].uids[0].id) } @@ -276,10 +303,10 @@ function createUserIdString(eids) { * @returns {String} */ function createQueryString(obj) { - let str = [] - for (var p in obj) { + const str = [] + for (const p in obj) { if (obj.hasOwnProperty(p)) { - let val = obj[p] + const val = obj[p] if (p !== 'schain' && p !== 'iab_content') { str.push(encodeURIComponent(p) + '=' + encodeURIComponent(val)) } else { @@ -296,11 +323,11 @@ function createQueryString(obj) { * @returns {String} */ function createTargetingString(obj) { - let str = [] - for (var p in obj) { + const str = [] + for (const p in obj) { if (obj.hasOwnProperty(p)) { - let key = p - let val = obj[p] + const key = p + const val = obj[p] str.push(key + '=' + val) } } @@ -334,8 +361,8 @@ function getContentObject(bid) { return bid.params.iabContent } - const globalContent = config.getConfig('ortb2.site') ? config.getConfig('ortb2.site.content') - : config.getConfig('ortb2.app.content') + const globalContent = deepAccess(bid, 'ortb2.site') ? deepAccess(bid, 'ortb2.site.content') + : deepAccess(bid, 'ortb2.app.content') if (globalContent && isPlainObject(globalContent)) { return globalContent } @@ -349,8 +376,8 @@ function getContentObject(bid) { */ function createIabContentString(iabContent) { const arrKeys = ['keywords', 'cat'] - let str = [] - for (let key in iabContent) { + const str = [] + for (const key in iabContent) { if (iabContent.hasOwnProperty(key)) { const value = (arrKeys.indexOf(key) !== -1 && Array.isArray(iabContent[key])) ? iabContent[key].map(node => encodeURIComponent(node)).join('|') : encodeURIComponent(iabContent[key]) @@ -383,4 +410,66 @@ function outstreamRender(bid) { }); } +/** + * Extract sizes for a given bid from either `mediaTypes` or `sizes` directly. + * + * @param {Object} bid + * @returns {string[]} + */ +function extractSizes(bid) { + const { mediaTypes } = bid // see https://docs.prebid.org/dev-docs/adunit-reference.html#examples + const sizes = [] + + if (isPlainObject(mediaTypes)) { + const { [BANNER]: bannerType } = mediaTypes + + // only applies for multi size Adslots -> BANNER + if (bannerType && isArray(bannerType.sizes)) { + if (isArray(bannerType.sizes[0])) { // multiple sizes given + sizes.push(bannerType.sizes) + } else { // just one size provided as array -> wrap to uniformly flatten later + sizes.push([bannerType.sizes]) + } + } + // The bid top level field `sizes` is deprecated and should not be used anymore. Keeping it for compatibility. + } else if (isArray(bid.sizes)) { + if (isArray(bid.sizes[0])) { + sizes.push(bid.sizes) + } else { + sizes.push([bid.sizes]) + } + } + + /** @type {Set} */ + const deduplicatedSizeStrings = new Set(sizes.flat().map(([width, height]) => width + DIMENSION_SIGN + height)) + + return Array.from(deduplicatedSizeStrings) +} + +/** + * Gets the floor price if the Price Floors Module is enabled for a given auction, + * which will add the getFloor() function to the bidRequest object. + * + * @param {Object} bid + * @param {string[]} sizes + * @returns The floor CPM of a matched rule based on the rule selection process (mediaType, size and currency), + * using the getFloor() inputs. Multi sizes and unsupported media types will default to '*' + */ +function getBidFloor(bid, sizes) { + if (!isFn(bid.getFloor)) { + return undefined; + } + const mediaTypes = deepAccess(bid, 'mediaTypes'); + const mediaType = mediaTypes !== undefined ? Object.keys(mediaTypes)[0].toLowerCase() : undefined; + const floor = bid.getFloor({ + currency: CURRENCY_CODE, + mediaType: mediaType !== undefined && spec.supportedMediaTypes.includes(mediaType) ? mediaType : '*', + size: sizes.length !== 1 ? '*' : extractSizes(sizes) + }); + if (floor.currency === CURRENCY_CODE) { + return floor.floor; + } + return undefined; +} + registerBidder(spec) diff --git a/modules/yieldliftBidAdapter.js b/modules/yieldliftBidAdapter.js index 61b99d95605..160d7e9a009 100644 --- a/modules/yieldliftBidAdapter.js +++ b/modules/yieldliftBidAdapter.js @@ -1,4 +1,4 @@ -import { deepSetValue, logInfo, deepAccess } from '../src/utils.js'; +import {deepSetValue, logInfo, deepAccess} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -44,9 +44,9 @@ export const spec = { id: bidderRequest.auctionId, imp: impressions, site: { - domain: window.location.hostname, - page: window.location.href, - ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null + domain: bidderRequest.refererInfo?.domain, + page: bidderRequest.refererInfo?.page, + ref: bidderRequest.refererInfo?.ref, }, ext: { exchange: { @@ -72,6 +72,12 @@ export const spec = { deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); } + // EIDS + const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); + if (Array.isArray(eids) && eids.length > 0) { + deepSetValue(openrtbRequest, 'user.ext.eids', eids); + } + const payloadString = JSON.stringify(openrtbRequest); return { method: 'POST', @@ -96,9 +102,7 @@ export const spec = { creativeId: bid.crid, netRevenue: DEFAULT_NET_REVENUE, currency: DEFAULT_CURRENCY, - meta: { - adomain: bid.adomain - } + meta: { advertiserDomains: bid && bid.advertiserDomains ? bid.advertiserDomains : [] } }) }) } else { diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index bc29f4822c8..892936ceea5 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -25,6 +25,7 @@ const TIME_TO_LIVE = 300; const NET_REVENUE = true; const BANNER_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; const VIDEO_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo'; +const PB_COOKIE_ASSIST_SYNC_ENDPOINT = `https://ads.yieldmo.com/pbcas`; const OUTSTREAM_VIDEO_PLAYER_URL = 'https://prebid-outstream.yieldmo.com/bundle.js'; const OPENRTB_VIDEO_BIDPARAMS = ['mimes', 'startdelay', 'placement', 'startdelay', 'skipafter', 'protocols', 'api', 'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable']; @@ -66,7 +67,8 @@ export const spec = { let serverRequest = { pbav: '$prebid.version$', p: [], - page_url: bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + page_url: bidderRequest.refererInfo.page, bust: new Date().getTime().toString(), dnt: getDNT(), description: getPageDescription(), @@ -176,8 +178,25 @@ export const spec = { return bids; }, - getUserSyncs: function () { - return []; + getUserSyncs: function(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { + const syncs = []; + const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; + const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; + const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; + const pbCookieAssistSyncUrl = `${PB_COOKIE_ASSIST_SYNC_ENDPOINT}?${usPrivacy}${gdprFlag}${gdprString}`; + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: pbCookieAssistSyncUrl + '&type=iframe' + }); + } else if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: pbCookieAssistSyncUrl + '&type=image' + }); + } + return syncs; } }; registerBidder(spec); @@ -223,6 +242,17 @@ function addPlacement(request) { if (gpid) { placementInfo.gpid = gpid; } + + // get the transaction id for the banner bid. + const transactionId = deepAccess(request, 'ortb2Imp.ext.tid'); + + if (transactionId) { + placementInfo.tid = transactionId; + } + + if (request.auctionId) { + placementInfo.auctionId = request.auctionId; + } return JSON.stringify(placementInfo); } @@ -352,7 +382,7 @@ function openRtbRequest(bidRequests, bidderRequest) { site: openRtbSite(bidRequests[0], bidderRequest), device: openRtbDevice(bidRequests[0]), badv: bidRequests[0].params.badv || [], - bcat: bidRequests[0].params.bcat || [], + bcat: deepAccess(bidderRequest, 'bcat') || bidRequests[0].params.bcat || [], ext: { prebid: '$prebid.version$', }, @@ -363,6 +393,9 @@ function openRtbRequest(bidRequests, bidderRequest) { openRtbRequest.schain = schain; } + if (bidRequests[0].auctionId) { + openRtbRequest.auctionId = bidRequests[0].auctionId; + } populateOpenRtbGdpr(openRtbRequest, bidderRequest); return openRtbRequest; @@ -380,7 +413,8 @@ function openRtbImpression(bidRequest) { tagid: bidRequest.adUnitCode, bidfloor: getBidFloor(bidRequest, VIDEO), ext: { - placement_id: bidRequest.params.placementId + placement_id: bidRequest.params.placementId, + tid: deepAccess(bidRequest, 'ortb2Imp.ext.tid') }, video: { w: size[0], @@ -445,13 +479,13 @@ function extractPlayerSize(bidRequest) { function openRtbSite(bidRequest, bidderRequest) { let result = {}; - const loc = parseUrl(deepAccess(bidderRequest, 'refererInfo.referer')); + const loc = parseUrl(deepAccess(bidderRequest, 'refererInfo.page')); if (!isEmpty(loc)) { result.page = `${loc.protocol}://${loc.hostname}${loc.pathname}`; } - if (self === top && document.referrer) { - result.ref = document.referrer; + if (bidderRequest.refererInfo?.ref) { + result.ref = bidderRequest.refererInfo.ref; } const keywords = document.getElementsByTagName('meta')['keywords']; @@ -512,13 +546,13 @@ function validateVideoParams(bid) { error += ' when ' + conditionStr; } throw new Error(error); - } + }; const paramInvalid = (paramStr, value, expectedStr) => { expectedStr = expectedStr ? ', expected: ' + expectedStr : ''; value = JSON.stringify(value); throw new Error(`"${paramStr}"=${value} is invalid${expectedStr}`); - } + }; const isDefined = val => typeof val !== 'undefined'; const validate = (fieldPath, validateCb, errorCb, errorCbParam) => { @@ -544,7 +578,7 @@ function validateVideoParams(bid) { } return value; } - } + }; try { validate('video.context', val => !isEmpty(val), paramRequired); diff --git a/modules/yieldnexusBidAdapter.md b/modules/yieldnexusBidAdapter.md deleted file mode 100644 index 675e8948a3e..00000000000 --- a/modules/yieldnexusBidAdapter.md +++ /dev/null @@ -1,53 +0,0 @@ -# Overview - -``` -Module Name: YieldNexus Bid Adapter -Module Type: Bidder Adapter -Maintainer: rtbops@yieldnexus.com -``` - -# Description - -Adds support to query the YieldNexus platform for bids. The YieldNexus platform supports banners & video. - -Only one parameter is required: `spid`, which provides your YieldNexus account number. - -# Test Parameters -``` -var adUnits = [ - // Banner: - { - code: 'banner-ad-unit', - sizes: [[300, 250]], - bids: [{ - bidder: 'yieldnexus', - params: { - spid: '1253', // your supply ID in your YieldNexus dashboard - bidfloor: 0.03, // an optional custom bid floor - adpos: 1, // ad position on the page (optional) - instl: 0 // interstitial placement? (0 or 1, optional) - } - }] - }, - // Outstream video: - { - code: 'video-ad-unit', - sizes: [[640, 480]], - mediaTypes: { - video: { - context: 'outstream', - playerSize: [640, 480] - } - }, - bids: [ { - bidder: 'yieldnexus', - params: { - spid: '1254', // your supply ID in your YieldNexus dashboard - bidfloor: 0.03, // an optional custom bid floor - adpos: 1, // ad position on the page (optional) - instl: 0 // interstitial placement? (0 or 1, optional) - } - }] - } -]; -``` diff --git a/modules/yieldoneAnalyticsAdapter.js b/modules/yieldoneAnalyticsAdapter.js index cb13503365e..0663ecb3f76 100644 --- a/modules/yieldoneAnalyticsAdapter.js +++ b/modules/yieldoneAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { isArray, deepClone } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import { targeting } from '../src/targeting.js'; @@ -99,7 +99,8 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { if (currentAuctionId) { const eventsStorage = yieldoneAnalytics.eventsStorage; if (!eventsStorage[currentAuctionId]) eventsStorage[currentAuctionId] = {events: []}; - const referrer = args.refererInfo && args.refererInfo.referer; + // TODO: is 'page' the right value here? + const referrer = args.refererInfo && args.refererInfo.page; if (referrer && referrers[currentAuctionId] !== referrer) { referrers[currentAuctionId] = referrer; } diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index 334de9eb3fa..c07911c9e1f 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -25,11 +25,14 @@ export const spec = { const params = bidRequest.params; const placementId = params.placementId; const cb = Math.floor(Math.random() * 99999999999); - const referrer = bidderRequest.refererInfo.referer; + // TODO: is 'page' the right value here? + const referrer = bidderRequest.refererInfo.page; const bidId = bidRequest.bidId; const transactionId = bidRequest.transactionId; const unitCode = bidRequest.adUnitCode; const timeout = config.getConfig('bidderTimeout'); + const language = window.navigator.language; + const screenSize = window.screen.width + 'x' + window.screen.height; const payload = { v: 'hb1', p: placementId, @@ -39,7 +42,9 @@ export const spec = { tid: transactionId, uc: unitCode, tmax: timeout, - t: 'i' + t: 'i', + language: language, + screen_size: screenSize }; const mediaType = getMediaType(bidRequest); @@ -68,11 +73,24 @@ export const spec = { payload.imuid = imuid; } + // DACID + const dacId = deepAccess(bidRequest, 'userId.dacId.id'); + if (isStr(dacId) && !isEmpty(dacId)) { + payload.dac_id = dacId; + payload.fuuid = dacId; + } + + // ID5 + const id5id = deepAccess(bidRequest, 'userId.id5id.uid'); + if (isStr(id5id) && !isEmpty(id5id)) { + payload.id5Id = id5id; + } + return { method: 'GET', url: ENDPOINT_URL, data: payload, - } + }; }); }, interpretResponse: function(serverResponse, bidRequest) { @@ -194,7 +212,7 @@ function getMediaType(bidRequest, enabledOldFormat = true) { } if (hasBannerType && hasVideoType) { - const playerParams = deepAccess(bidRequest, 'params.playerParams') + const playerParams = deepAccess(bidRequest, 'params.playerParams'); if (playerParams) { return VIDEO; } else { diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index 6872820dd48..31c6daae7f6 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -1,6 +1,6 @@ import {buildUrl, generateUUID, getWindowLocation, logError, logInfo, parseSizesInput, parseUrl} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import {getStorageManager} from '../src/storageManager.js'; @@ -18,7 +18,8 @@ const events = { const localStoragePrefix = 'yuktamediaAnalytics_'; const utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; const location = getWindowLocation(); -const referer = getRefererInfo().referer; +// TODO: is 'page' the right value here? +const referer = getRefererInfo().page; const _pageInfo = { userAgent: window.navigator.userAgent, timezoneOffset: new Date().getTimezoneOffset(), diff --git a/modules/zedoBidAdapter.md b/modules/zedoBidAdapter.md deleted file mode 100644 index 2f31e8aed9b..00000000000 --- a/modules/zedoBidAdapter.md +++ /dev/null @@ -1,65 +0,0 @@ -# Overview - -Module Name: ZEDO Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebidsupport@zedo.com - -# Description - -Module that connects to ZEDO's demand sources. - -ZEDO supports both display and video. -For video integration, ZEDO returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction - -ZEDO has its own renderer and will render the video unit if not defined in the config. - - -# Test Parameters -# display -``` - - var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - // Replace this object to test a new Adapter! - bids: [{ - bidder: 'zedo', - params: { - channelCode: 2264004735, //REQUIRED - dimId:9 //REQUIRED - } - }] - - }]; -``` -# video -``` - - var adUnit1 = [ - { - code: 'videoAdUnit', - mediaTypes: - { - video: - { - context: 'outstream', - playerSize: [640, 480] - } - }, - bids: [ - { - bidder: 'zedo', - params: - { - channelCode: 2264004735, // required - dimId: 85, // required - pubId: 1 // optional - } - } - ] - }]; -``` \ No newline at end of file diff --git a/modules/zetaBidAdapter.js b/modules/zetaBidAdapter.js index 27650888677..159ea42cead 100644 --- a/modules/zetaBidAdapter.js +++ b/modules/zetaBidAdapter.js @@ -1,4 +1,4 @@ -import { logWarn } from '../src/utils.js'; +import { logWarn, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; const BIDDER_CODE = 'zeta_global'; @@ -84,7 +84,7 @@ export const spec = { allimps: params.allimps, cur: [DEFAULT_CUR], wlang: params.wlang, - bcat: params.bcat, + bcat: deepAccess(bidderRequest.ortb2Imp, 'bcat') || params.bcat, badv: params.badv, bapp: params.bapp, source: params.source ? params.source : {}, @@ -94,7 +94,7 @@ export const spec = { payload.device.ua = navigator.userAgent; payload.device.ip = navigator.ip; - payload.site.page = bidderRequest.refererInfo.referer; + payload.site.page = bidderRequest.refererInfo.page; payload.site.mobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; payload.ext.definerId = params.definerId; diff --git a/modules/zetaSspBidAdapter.md b/modules/zetaSspBidAdapter.md deleted file mode 100644 index 00d8663586c..00000000000 --- a/modules/zetaSspBidAdapter.md +++ /dev/null @@ -1,74 +0,0 @@ -# Overview - -``` -Module Name: Zeta Ssp Bidder Adapter -Module Type: Bidder Adapter -Maintainer: miakovlev@zetaglobal.com -``` - -# Description - -Module that connects to Zeta's SSP - -# Banner Ad Unit: For Publishers -``` - var adUnits = [ - { - mediaTypes: { - banner: { - sizes: [[300, 250]], // a display size - } - }, - bids: [ - { - bidder: 'zeta_global_ssp', - bidId: 12345, - params: { - placement: 12345, - user: { - uid: 12345, - buyeruid: 12345 - }, - tags: { - someTag: 123, - sid: 'publisherId' - }, - test: 1 - } - } - ] - } - ]; -``` - -# Video Ad Unit: For Publishers -``` - var adUnits = [ - { - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream' - } - }, - bids: [ - { - bidder: 'zeta_global_ssp', - bidId: 12345, - params: { - placement: 12345, - user: { - uid: 12345, - buyeruid: 12345 - }, - tags: { - someTag: 123, - sid: 'publisherId' - }, - test: 1 - } - } - ] - } - ]; -``` diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 906e6e19cc2..ee3bb9cd5d6 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -3,7 +3,7 @@ import { ajax } from '../src/ajax.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import adapter from '../src/AnalyticsAdapter.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; const ZETA_GVL_ID = 833; const ADAPTER_CODE = 'zeta_global_ssp'; diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 114bb86b4d6..4689683fbc7 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -2,9 +2,10 @@ import {deepAccess, deepSetValue, isArray, isBoolean, isNumber, isStr, logWarn} import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import {parseDomain} from '../src/refererDetection.js'; const BIDDER_CODE = 'zeta_global_ssp'; -const ENDPOINT_URL = 'https://ssp.disqus.com/bid'; +const ENDPOINT_URL = 'https://ssp.disqus.com/bid/prebid'; const USER_SYNC_URL_IFRAME = 'https://ssp.disqus.com/sync?type=iframe'; const USER_SYNC_URL_IMAGE = 'https://ssp.disqus.com/sync?type=image'; const DEFAULT_CUR = 'USD'; @@ -68,34 +69,36 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { const secure = 1; // treat all requests as secure - const request = validBidRequests[0]; - const params = request.params; - const impData = { - id: request.bidId, - secure: secure - }; - if (request.mediaTypes) { - for (const mediaType in request.mediaTypes) { - switch (mediaType) { - case BANNER: - impData.banner = buildBanner(request); - break; - case VIDEO: - impData.video = buildVideo(request); - break; + const params = validBidRequests[0].params; + const imps = validBidRequests.map(request => { + const impData = { + id: request.bidId, + secure: secure + }; + if (request.mediaTypes) { + for (const mediaType in request.mediaTypes) { + switch (mediaType) { + case BANNER: + impData.banner = buildBanner(request); + break; + case VIDEO: + impData.video = buildVideo(request); + break; + } } } - } - if (!impData.banner && !impData.video) { - impData.banner = buildBanner(request); - } - const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + if (!impData.banner && !impData.video) { + impData.banner = buildBanner(request); + } + return impData; + }); + let payload = { id: bidderRequest.auctionId, cur: [DEFAULT_CUR], - imp: [impData], + imp: imps, site: params.site ? params.site : {}, - device: {...fpd.device, ...params.device}, + device: {...(bidderRequest.ortb2?.device || {}), ...params.device}, user: params.user ? params.user : {}, app: params.app ? params.app : {}, ext: { @@ -104,8 +107,9 @@ export const spec = { } }; const rInfo = bidderRequest.refererInfo; - payload.site.page = config.getConfig('pageUrl') || ((rInfo && rInfo.referer) ? rInfo.referer.trim() : window.location.href); - payload.site.domain = config.getConfig('publisherDomain') || getDomainFromURL(payload.site.page); + // TODO: do the fallbacks make sense here? + payload.site.page = rInfo.page || rInfo.topmostLocation; + payload.site.domain = parseDomain(payload.site.page, {noLeadingWww: true}); payload.device.ua = navigator.userAgent; payload.device.language = navigator.language; @@ -125,7 +129,7 @@ export const spec = { deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - provideEids(request, payload); + provideEids(validBidRequests[0], payload); const url = params.shortname ? ENDPOINT_URL.concat('?shortname=', params.shortname) : ENDPOINT_URL; return { method: 'POST', @@ -269,16 +273,6 @@ function provideEids(request, payload) { } } -function getDomainFromURL(url) { - let anchor = document.createElement('a'); - anchor.href = url; - let hostname = anchor.hostname; - if (hostname.indexOf('www.') === 0) { - return hostname.substring(4); - } - return hostname; -} - function provideMediaType(zetaBid, bid) { if (zetaBid.ext && zetaBid.ext.bidtype) { if (zetaBid.ext.bidtype === VIDEO) { diff --git a/package-lock.json b/package-lock.json index 87441d932f0..1665b80db6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,23 +1,24 @@ { "name": "prebid.js", - "version": "6.29.0-pre", + "version": "7.16.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "6.25.0-pre", + "version": "7.15.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", + "@babel/plugin-transform-runtime": "^7.18.9", "@babel/preset-env": "^7.16.8", - "babel-plugin-transform-object-assign": "^6.22.0", + "@babel/runtime": "^7.18.9", "core-js": "^3.13.0", "core-js-pure": "^3.13.0", "criteo-direct-rsa-validate": "^1.1.0", "crypto-js": "^3.3.0", "dlv": "1.1.3", - "dset": "2.0.1", + "dset": "3.1.2", "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", @@ -25,7 +26,6 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", - "@jsdevtools/coverage-istanbul-loader": "^3.0.3", "@wdio/browserstack-service": "^7.16.0", "@wdio/cli": "^7.5.2", "@wdio/concise-reporter": "^7.5.2", @@ -36,12 +36,13 @@ "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", + "babel-plugin-istanbul": "^6.1.1", "babel-register": "^6.26.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", "deep-equal": "^2.0.3", - "documentation": "^13.2.5", + "documentation": "^14.0.0", "es5-shim": "^4.5.14", "eslint": "^7.27.0", "eslint-config-standard": "^10.2.1", @@ -57,9 +58,7 @@ "gulp-clean": "^0.3.2", "gulp-concat": "^2.6.0", "gulp-connect": "^5.7.0", - "gulp-eslint": "^4.0.0", - "gulp-footer": "^2.0.2", - "gulp-header": "^2.0.9", + "gulp-eslint": "^6.0.0", "gulp-if": "^3.0.0", "gulp-js-escape": "^1.0.1", "gulp-replace": "^1.0.0", @@ -100,6 +99,7 @@ "webdriverio": "^7.6.1", "webpack": "^5.70.0", "webpack-bundle-analyzer": "^4.5.0", + "webpack-manifest-plugin": "^5.0.0", "webpack-stream": "^7.0.0", "yargs": "^1.3.1" }, @@ -122,43 +122,43 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", "dependencies": { - "@babel/highlight": "^7.16.7" + "@babel/highlight": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz", + "integrity": "sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz", + "integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==", "dependencies": { "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.17.2", - "@babel/parser": "^7.17.3", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.0", + "@babel/helper-compilation-targets": "^7.19.0", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helpers": "^7.19.0", + "@babel/parser": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", + "json5": "^2.2.1", "semver": "^6.3.0" }, "engines": { @@ -188,13 +188,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", + "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", "dependencies": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.19.0", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" @@ -224,13 +224,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz", + "integrity": "sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==", "dependencies": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", + "@babel/compat-data": "^7.19.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", "semver": "^6.3.0" }, "engines": { @@ -294,12 +294,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dependencies": { - "@babel/types": "^7.16.7" - }, + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", "engines": { "node": ">=6.9.0" } @@ -316,35 +313,23 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", "dependencies": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dependencies": { - "@babel/types": "^7.16.7" + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -362,29 +347,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", - "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", + "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" }, "engines": { "node": ">=6.9.0" @@ -402,9 +387,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==", "engines": { "node": ">=6.9.0" } @@ -438,11 +423,11 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" @@ -460,28 +445,36 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", "engines": { "node": ">=6.9.0" } @@ -501,24 +494,24 @@ } }, "node_modules/@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", + "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", "dependencies": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.0", - "@babel/types": "^7.17.0" + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -527,9 +520,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", + "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1337,6 +1330,25 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.9.tgz", + "integrity": "sha512-wS8uJwBt7/b/mzE13ktsJdmS4JP/j7PQSaADtnb4I2wL0zK51MQ0pmF8/Jy0wUIS96fr+fXT6S/ifiPXnvrlSg==", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "babel-plugin-polyfill-corejs2": "^0.3.1", + "babel-plugin-polyfill-corejs3": "^0.5.2", + "babel-plugin-polyfill-regenerator": "^0.3.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", @@ -1540,9 +1552,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", - "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -1556,31 +1568,31 @@ "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "node_modules/@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", - "@babel/types": "^7.17.0", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz", + "integrity": "sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.19.0", + "@babel/types": "^7.19.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1589,11 +1601,12 @@ } }, "node_modules/@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", + "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1809,13 +1822,90 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@hutson/parse-repository-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", - "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, "engines": { - "node": ">=6.9.0" + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/@istanbuljs/schema": { @@ -1904,6 +1994,19 @@ "node": ">=8" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", @@ -1912,33 +2015,38 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.11", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", - "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@jsdevtools/coverage-istanbul-loader": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", - "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", - "dev": true, - "dependencies": { - "convert-source-map": "^1.7.0", - "istanbul-lib-instrument": "^4.0.3", - "loader-utils": "^2.0.0", - "merge-source-map": "^1.1.0", - "schema-utils": "^2.7.0" - } - }, "node_modules/@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -2049,6 +2157,15 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", "dev": true }, + "node_modules/@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/diff": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.0.2.tgz", @@ -2099,6 +2216,12 @@ "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", "dev": true }, + "node_modules/@types/extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.1.tgz", + "integrity": "sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==", + "dev": true + }, "node_modules/@types/fibers": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/fibers/-/fibers-3.1.1.tgz", @@ -2114,6 +2237,21 @@ "@types/node": "*" } }, + "node_modules/@types/github-slugger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz", + "integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==", + "dev": true + }, + "node_modules/@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", @@ -2217,10 +2355,10 @@ "@types/unist": "*" } }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", "dev": true }, "node_modules/@types/mocha": { @@ -2229,6 +2367,12 @@ "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", "dev": true }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, "node_modules/@types/node": { "version": "17.0.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", @@ -2370,14 +2514,14 @@ "dev": true }, "node_modules/@vue/compiler-core": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.31.tgz", - "integrity": "sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.38.tgz", + "integrity": "sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==", "dev": true, "optional": true, "dependencies": { "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.31", + "@vue/shared": "3.2.38", "estree-walker": "^2.0.2", "source-map": "^0.6.1" } @@ -2393,29 +2537,29 @@ } }, "node_modules/@vue/compiler-dom": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz", - "integrity": "sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz", + "integrity": "sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==", "dev": true, "optional": true, "dependencies": { - "@vue/compiler-core": "3.2.31", - "@vue/shared": "3.2.31" + "@vue/compiler-core": "3.2.38", + "@vue/shared": "3.2.38" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz", - "integrity": "sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz", + "integrity": "sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==", "dev": true, "optional": true, "dependencies": { "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.31", - "@vue/compiler-dom": "3.2.31", - "@vue/compiler-ssr": "3.2.31", - "@vue/reactivity-transform": "3.2.31", - "@vue/shared": "3.2.31", + "@vue/compiler-core": "3.2.38", + "@vue/compiler-dom": "3.2.38", + "@vue/compiler-ssr": "3.2.38", + "@vue/reactivity-transform": "3.2.38", + "@vue/shared": "3.2.38", "estree-walker": "^2.0.2", "magic-string": "^0.25.7", "postcss": "^8.1.10", @@ -2433,34 +2577,34 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz", - "integrity": "sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz", + "integrity": "sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==", "dev": true, "optional": true, "dependencies": { - "@vue/compiler-dom": "3.2.31", - "@vue/shared": "3.2.31" + "@vue/compiler-dom": "3.2.38", + "@vue/shared": "3.2.38" } }, "node_modules/@vue/reactivity-transform": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz", - "integrity": "sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz", + "integrity": "sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==", "dev": true, "optional": true, "dependencies": { "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.31", - "@vue/shared": "3.2.31", + "@vue/compiler-core": "3.2.38", + "@vue/shared": "3.2.38", "estree-walker": "^2.0.2", "magic-string": "^0.25.7" } }, "node_modules/@vue/shared": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.31.tgz", - "integrity": "sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.38.tgz", + "integrity": "sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg==", "dev": true, "optional": true }, @@ -3546,32 +3690,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, - "dependencies": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/add-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", - "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", - "dev": true - }, "node_modules/agent-base": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", @@ -3643,18 +3761,6 @@ "node": ">=0.10.0" } }, - "node_modules/ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3881,12 +3987,6 @@ "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", "dev": true }, - "node_modules/array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", - "dev": true - }, "node_modules/array-includes": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", @@ -4007,15 +4107,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -4407,6 +4498,38 @@ "object.assign": "^4.1.0" } }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", @@ -4443,14 +4566,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/babel-plugin-transform-object-assign": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.22.0.tgz", - "integrity": "sha1-+Z0vZvGgsNSY40bFNZaEdAyqILo=", - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, "node_modules/babel-register": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", @@ -4470,7 +4585,7 @@ "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", "dev": true, "hasInstallScript": true }, @@ -4499,6 +4614,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, "dependencies": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -4508,7 +4624,8 @@ "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, "hasInstallScript": true }, "node_modules/babel-template": { @@ -4586,18 +4703,6 @@ "node": ">=0.10.0" } }, - "node_modules/babelify": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", - "integrity": "sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", @@ -4628,9 +4733,9 @@ } }, "node_modules/bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "dev": true, "funding": { "type": "github", @@ -4923,21 +5028,6 @@ "node": ">=8" } }, - "node_modules/browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "dependencies": { - "resolve": "1.1.7" - } - }, - "node_modules/browser-resolve/node_modules/resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -4945,25 +5035,30 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.0.tgz", - "integrity": "sha512-bnpOoa+DownbciXj0jVGENf8VYQnE2LNWomhYuCsMmmx9Jd9lwq0WXODuwpSsp8AVdKM2/HorrzxAfbKvWTByQ==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], "dependencies": { - "caniuse-lite": "^1.0.30001313", - "electron-to-chromium": "^1.4.76", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" }, "bin": { "browserslist": "cli.js" }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" } }, "node_modules/browserstack": { @@ -5125,12 +5220,6 @@ "node": ">=0.10" } }, - "node_modules/buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", - "dev": true - }, "node_modules/buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -5438,12 +5527,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cached-path-relative": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz", - "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==", - "dev": true - }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -5456,27 +5539,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "dependencies": { - "callsites": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/caller-path/node_modules/callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -5495,36 +5557,10 @@ "node": ">=6" } }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/caniuse-lite": { - "version": "1.0.30001320", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz", - "integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==", + "version": "1.0.30001390", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz", + "integrity": "sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g==", "funding": [ { "type": "opencollective", @@ -5543,9 +5579,9 @@ "dev": true }, "node_modules/ccount": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", - "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "dev": true, "funding": { "type": "github", @@ -5615,9 +5651,9 @@ } }, "node_modules/character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "dev": true, "funding": { "type": "github", @@ -5625,9 +5661,9 @@ } }, "node_modules/character-entities-html4": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", - "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "dev": true, "funding": { "type": "github", @@ -5635,19 +5671,9 @@ } }, "node_modules/character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "dev": true, "funding": { "type": "github", @@ -5741,13 +5767,6 @@ "node": ">=6.0" } }, - "node_modules/circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "deprecated": "CircularJSON is in maintenance only, flatted is its successor.", - "dev": true - }, "node_modules/class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -5931,16 +5950,6 @@ "readable-stream": "^2.3.5" } }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, "node_modules/code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -6021,9 +6030,9 @@ } }, "node_modules/comma-separated-tokens": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", - "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", + "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", "dev": true, "funding": { "type": "github", @@ -6042,16 +6051,6 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "node_modules/compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dev": true, - "dependencies": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -6209,395 +6208,6 @@ "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", "dev": true }, - "node_modules/conventional-changelog": { - "version": "3.1.24", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.24.tgz", - "integrity": "sha512-ed6k8PO00UVvhExYohroVPXcOJ/K1N0/drJHx/faTH37OIZthlecuLIRX/T6uOp682CAoVoFpu+sSEaeuH6Asg==", - "dev": true, - "dependencies": { - "conventional-changelog-angular": "^5.0.12", - "conventional-changelog-atom": "^2.0.8", - "conventional-changelog-codemirror": "^2.0.8", - "conventional-changelog-conventionalcommits": "^4.5.0", - "conventional-changelog-core": "^4.2.1", - "conventional-changelog-ember": "^2.0.9", - "conventional-changelog-eslint": "^3.0.9", - "conventional-changelog-express": "^2.0.6", - "conventional-changelog-jquery": "^3.0.11", - "conventional-changelog-jshint": "^2.0.9", - "conventional-changelog-preset-loader": "^2.3.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-angular": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", - "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-atom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", - "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-codemirror": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", - "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-config-spec": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz", - "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==", - "dev": true - }, - "node_modules/conventional-changelog-conventionalcommits": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.1.tgz", - "integrity": "sha512-lzWJpPZhbM1R0PIzkwzGBCnAkH5RKJzJfFQZcl/D+2lsJxAwGnDKBqn/F4C1RD31GJNn8NuKWQzAZDAVXPp2Mw==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-core": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz", - "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==", - "dev": true, - "dependencies": { - "add-stream": "^1.0.0", - "conventional-changelog-writer": "^5.0.0", - "conventional-commits-parser": "^3.2.0", - "dateformat": "^3.0.0", - "get-pkg-repo": "^4.0.0", - "git-raw-commits": "^2.0.8", - "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^4.1.1", - "lodash": "^4.17.15", - "normalize-package-data": "^3.0.0", - "q": "^1.5.1", - "read-pkg": "^3.0.0", - "read-pkg-up": "^3.0.0", - "through2": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-core/node_modules/dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/conventional-changelog-core/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-core/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-core/node_modules/read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/conventional-changelog-core/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-ember": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", - "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-eslint": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", - "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-express": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", - "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-jquery": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", - "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", - "dev": true, - "dependencies": { - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-jshint": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", - "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-preset-loader": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", - "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", - "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", - "dev": true, - "dependencies": { - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-changelog-writer": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-changelog-writer/node_modules/dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/conventional-changelog-writer/node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/conventional-commits-filter": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", - "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", - "dev": true, - "dependencies": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-parser": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", - "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", - "dev": true, - "dependencies": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "conventional-commits-parser": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-commits-parser/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/conventional-commits-parser/node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/conventional-recommended-bump": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", - "integrity": "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==", - "dev": true, - "dependencies": { - "concat-stream": "^2.0.0", - "conventional-changelog-preset-loader": "^2.3.4", - "conventional-commits-filter": "^2.0.7", - "conventional-commits-parser": "^3.2.0", - "git-raw-commits": "^2.0.8", - "git-semver-tags": "^4.1.1", - "meow": "^8.0.0", - "q": "^1.5.1" - }, - "bin": { - "conventional-recommended-bump": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/conventional-recommended-bump/node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "dev": true, - "engines": [ - "node >= 6.0" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/conventional-recommended-bump/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -6642,6 +6252,7 @@ "version": "3.21.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.21.1.tgz", "integrity": "sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -6673,6 +6284,7 @@ "version": "3.21.1", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", + "deprecated": "core-js-pure@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js-pure.", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -6853,15 +6465,6 @@ "type": "^1.0.1" } }, - "node_modules/dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -6895,7 +6498,7 @@ "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true, "optional": true }, @@ -6944,26 +6547,17 @@ "node": ">=0.10.0" } }, - "node_modules/decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", "dev": true, "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" + "character-entities": "^2.0.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true, - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/decode-uri-component": { @@ -7127,12 +6721,6 @@ "node": ">=0.10.0" } }, - "node_modules/defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7150,6 +6738,15 @@ "node": ">= 0.6" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -7164,15 +6761,6 @@ "node": ">=0.10.0" } }, - "node_modules/detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -7194,23 +6782,6 @@ "node": ">=0.10.0" } }, - "node_modules/detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", - "dev": true, - "dependencies": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" - }, - "bin": { - "detective": "bin/detective.js" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/devtools": { "version": "7.16.16", "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.16.tgz", @@ -7323,308 +6894,176 @@ } }, "node_modules/documentation": { - "version": "13.2.5", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-13.2.5.tgz", - "integrity": "sha512-d1TrfrHXYZR63xrOzkYwwe297vkSwBoEhyyMBOi20T+7Ohe1aX1dW4nqXncQmdmE5MxluSaxxa3BW1dCvbF5AQ==", - "dev": true, - "dependencies": { - "@babel/core": "7.12.3", - "@babel/generator": "7.12.1", - "@babel/parser": "7.12.3", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "ansi-html": "^0.0.7", - "babelify": "^10.0.0", - "chalk": "^2.3.0", - "chokidar": "^3.4.0", - "concat-stream": "^1.6.0", - "diff": "^4.0.1", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.0.tgz", + "integrity": "sha512-4AwFzdiseEdtqR0KKLrruIQ5fvh7n5epg47P0ZyOidA5Fes5am+6xjqkDECHPwcv4pxJ6zITaHwzCoGblP0+JQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.18.10", + "@babel/generator": "^7.18.10", + "@babel/parser": "^7.18.11", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10", + "chalk": "^5.0.1", + "chokidar": "^3.5.3", + "diff": "^5.1.0", "doctrine-temporary-fork": "2.1.0", - "get-port": "^5.0.0", - "git-url-parse": "^11.1.2", - "github-slugger": "1.2.0", - "glob": "^7.1.2", - "globals-docs": "^2.4.0", - "highlight.js": "^10.7.2", - "ini": "^1.3.5", - "js-yaml": "^3.10.0", - "lodash": "^4.17.10", - "mdast-util-find-and-replace": "^1.1.1", + "git-url-parse": "^12.0.0", + "github-slugger": "1.4.0", + "glob": "^8.0.3", + "globals-docs": "^2.4.1", + "highlight.js": "^11.6.0", + "ini": "^3.0.0", + "js-yaml": "^4.1.0", + "konan": "^2.1.1", + "lodash": "^4.17.21", + "mdast-util-find-and-replace": "^2.2.1", "mdast-util-inject": "^1.1.0", - "micromatch": "^3.1.5", - "mime": "^2.2.0", - "module-deps-sortable": "^5.0.3", + "micromark-util-character": "^1.1.0", "parse-filepath": "^1.0.2", - "pify": "^5.0.0", - "read-pkg-up": "^4.0.0", - "remark": "^13.0.0", - "remark-gfm": "^1.0.0", - "remark-html": "^13.0.1", - "remark-reference-links": "^5.0.0", - "remark-toc": "^7.2.0", - "resolve": "^1.8.1", - "stream-array": "^1.1.2", - "strip-json-comments": "^2.0.1", - "tiny-lr": "^1.1.0", - "unist-builder": "^2.0.3", - "unist-util-visit": "^2.0.3", - "vfile": "^4.0.0", - "vfile-reporter": "^6.0.0", - "vfile-sort": "^2.1.0", - "vinyl": "^2.1.0", - "vinyl-fs": "^3.0.2", - "yargs": "^15.3.1" + "pify": "^6.0.0", + "read-pkg-up": "^9.1.0", + "remark": "^14.0.2", + "remark-gfm": "^3.0.1", + "remark-html": "^15.0.1", + "remark-reference-links": "^6.0.1", + "remark-toc": "^8.0.1", + "resolve": "^1.22.1", + "strip-json-comments": "^5.0.0", + "unist-builder": "^3.0.0", + "unist-util-visit": "^4.1.0", + "vfile": "^5.3.4", + "vfile-reporter": "^7.0.4", + "vfile-sort": "^3.0.0", + "yargs": "^17.5.1" }, "bin": { "documentation": "bin/documentation.js" }, "engines": { - "node": ">=10" + "node": ">=14" }, "optionalDependencies": { - "@vue/compiler-sfc": "^3.0.11", - "vue-template-compiler": "^2.6.12" + "@vue/compiler-sfc": "^3.2.37", + "vue-template-compiler": "^2.7.8" } }, - "node_modules/documentation/node_modules/@babel/core": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", - "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.1", - "@babel/parser": "^7.12.3", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } + "node_modules/documentation/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, - "node_modules/documentation/node_modules/@babel/generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", - "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", + "node_modules/documentation/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "@babel/types": "^7.12.1", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/documentation/node_modules/@babel/parser": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", - "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" + "balanced-match": "^1.0.0" } }, - "node_modules/documentation/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/documentation/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/documentation/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/documentation/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/documentation/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/documentation/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", "dev": true, "engines": { "node": ">=0.3.1" } }, - "node_modules/documentation/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/documentation/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/documentation/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" + "node": ">=12" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/documentation/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/documentation/node_modules/ini": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", + "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/documentation/node_modules/p-locate": { + "node_modules/documentation/node_modules/js-yaml": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/documentation/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/documentation/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/documentation/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, "bin": { - "semver": "bin/semver" + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/documentation/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/documentation/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/documentation/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/documentation/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "node_modules/documentation/node_modules/strip-json-comments": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", + "integrity": "sha512-V1LGY4UUo0jgwC+ELQ2BNWfPa17TIuwBLg+j1AA/9RPzKINl1lhxVEu2r+ZTTO8aetIsUzE5Qj6LMSBkoGYKKw==", "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/documentation/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "node_modules/documentation/node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "dev": true, "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" }, "engines": { - "node": ">=6" + "node": ">=12" } }, "node_modules/dom-serialize": { @@ -7639,97 +7078,10 @@ "void-elements": "^2.0.0" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dotgitignore": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", - "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dotgitignore/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dotgitignore/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dotgitignore/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dotgitignore/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dotgitignore/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/dset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/dset/-/dset-2.0.1.tgz", - "integrity": "sha512-nI29OZMRYq36hOcifB6HTjajNAAiBKSXsyWZrq+VniusseuP2OpNlTiYgsaNRSGvpyq5Wjbc2gQLyBdTyWqhnQ==", - "deprecated": "Please use dset@2.1.0 or later for an important security patch", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", + "integrity": "sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==", "engines": { "node": ">=4" } @@ -7797,6 +7149,12 @@ "node": ">=0.10.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "node_modules/easy-table": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", @@ -7835,12 +7193,12 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/ejs": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", - "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", + "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", "dev": true, "dependencies": { - "jake": "^10.6.1" + "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" @@ -7850,14 +7208,14 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.78", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.78.tgz", - "integrity": "sha512-o61+D/Lx7j/E0LIin/efOqeHpXhwi1TaQco9vUcRmr91m25SfZY6L5hWJDv/r+6kNjboFKgBw1LbfM0lbhuK6Q==" + "version": "1.4.243", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.243.tgz", + "integrity": "sha512-BgLD2gBX43OSXwlT01oYRRD5NIB4n3okTRxkzEAC6G0SZG4TTlyrWMjbOo0fajCwqwpRtMHXQNMjtRN6qpNtfw==" }, "node_modules/emoji-regex": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", - "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, "node_modules/emojis-list": { @@ -9227,6 +8585,18 @@ "node": ">=4" } }, + "node_modules/external-editor/node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -9431,12 +8801,33 @@ "optional": true }, "node_modules/filelist": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", - "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, "dependencies": { - "minimatch": "^3.0.4" + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, "node_modules/fill-range": { @@ -9451,15 +8842,6 @@ "node": ">=8" } }, - "node_modules/filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -9725,18 +9107,6 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, - "node_modules/fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", - "dev": true, - "dependencies": { - "null-check": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -9956,71 +9326,13 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-pkg-repo": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", - "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", - "dev": true, - "dependencies": { - "@hutson/parse-repository-url": "^3.0.0", - "hosted-git-info": "^4.0.0", - "through2": "^2.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "get-pkg-repo": "src/cli.js" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-pkg-repo/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-pkg-repo/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/get-pkg-repo/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-pkg-repo/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "engines": { - "node": ">=10" + "node": ">=8.0.0" } }, "node_modules/get-port": { @@ -10090,122 +9402,30 @@ "assert-plus": "^1.0.0" } }, - "node_modules/git-raw-commits": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", - "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", - "dev": true, - "dependencies": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/git-raw-commits/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/git-raw-commits/node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/git-remote-origin-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", - "dev": true, - "dependencies": { - "gitconfiglocal": "^1.0.0", - "pify": "^2.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/git-remote-origin-url/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/git-semver-tags": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", - "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", - "dev": true, - "dependencies": { - "meow": "^8.0.0", - "semver": "^6.0.0" - }, - "bin": { - "git-semver-tags": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/git-up": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.5.tgz", - "integrity": "sha512-YUvVDg/vX3d0syBsk/CKUTib0srcQME0JyHkL5BaYdwLsiCslPWmDSi8PUMo9pXYjrryMcmsCoCgsTpSCJEQaA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-6.0.0.tgz", + "integrity": "sha512-6RUFSNd1c/D0xtGnyWN2sxza2bZtZ/EmI9448n6rCZruFwV/ezeEn2fJP7XnUQGwf0RAtd/mmUCbtH6JPYA2SA==", "dev": true, "dependencies": { - "is-ssh": "^1.3.0", - "parse-url": "^6.0.0" + "is-ssh": "^1.4.0", + "parse-url": "^7.0.2" } }, "node_modules/git-url-parse": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.6.0.tgz", - "integrity": "sha512-WWUxvJs5HsyHL6L08wOusa/IXYtMuCAhrMmnTjQPpBU0TTHyDhnOATNH3xNQz7YOQUsqIIPTGr4xiVti1Hsk5g==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-12.0.0.tgz", + "integrity": "sha512-I6LMWsxV87vysX1WfsoglXsXg6GjQRKq7+Dgiseo+h0skmp5Hp2rzmcEIRQot9CPA+uzU7x1x7jZdqvTFGnB+Q==", "dev": true, "dependencies": { - "git-up": "^4.0.0" - } - }, - "node_modules/gitconfiglocal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", - "dev": true, - "dependencies": { - "ini": "^1.3.2" + "git-up": "^6.0.0" } }, "node_modules/github-slugger": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.2.0.tgz", - "integrity": "sha512-wIaa75k1vZhyPm9yWrD08A5Xnx/V+RmzGrpjQuLemGKSb77Qukiaei58Bogrl/LZSADDfPzKJX8jhLs4CRTl7Q==", - "dev": true, - "dependencies": { - "emoji-regex": ">=6.0.0 <=6.1.1" - } + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", + "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==", + "dev": true }, "node_modules/glob": { "version": "7.2.0", @@ -10242,7 +9462,7 @@ "node_modules/glob-stream": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", "dev": true, "dependencies": { "extend": "^3.0.0", @@ -10263,7 +9483,7 @@ "node_modules/glob-stream/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", "dev": true, "dependencies": { "is-glob": "^3.1.0", @@ -10430,7 +9650,7 @@ "node_modules/glob-watcher/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", "dev": true, "dependencies": { "is-glob": "^3.1.0", @@ -10612,9 +9832,9 @@ } }, "node_modules/got": { - "version": "11.8.3", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz", - "integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==", + "version": "11.8.5", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", + "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", "dev": true, "dependencies": { "@sindresorhus/is": "^4.0.0", @@ -10678,7 +9898,7 @@ "node_modules/gulp-clean": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.3.2.tgz", - "integrity": "sha1-o0fUc6zqQBgvk1WHpFGUFnGSgQI=", + "integrity": "sha512-1pSOrC+PLKkEQz1HFl5L+TG0LV3/Tz/Y25lRk1IjTsE/agXWcqeeicBFJ1vDgokF+musc0vSBdm3xLXUU8cP1Q==", "dev": true, "dependencies": { "gulp-util": "^2.2.14", @@ -10783,7 +10003,7 @@ "node_modules/gulp-clean/node_modules/gulp-util": { "version": "2.2.20", "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", - "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", + "integrity": "sha512-9rtv4sj9EtCWYGD15HQQvWtRBtU9g1t0+w29tphetHxjxEAuBKQJkhGqvlLkHEtUjEgoqIpsVwPKU1yMZAa+wA==", "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", "dev": true, "dependencies": { @@ -10887,7 +10107,7 @@ "node_modules/gulp-clean/node_modules/lodash.template": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", - "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", + "integrity": "sha512-5yLOQwlS69xbaez3g9dA1i0GMAj8pLDHp8lhA4V7M1vRam1lqD76f0jg5EV+65frbqrXo1WH9ZfKalfYBzJ5yQ==", "dev": true, "dependencies": { "lodash._escapestringchar": "~2.4.1", @@ -10921,7 +10141,7 @@ "node_modules/gulp-clean/node_modules/meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "integrity": "sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==", "dev": true, "dependencies": { "camelcase-keys": "^2.0.0", @@ -10940,9 +10160,9 @@ } }, "node_modules/gulp-clean/node_modules/meow/node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/gulp-clean/node_modules/minimist": { @@ -11141,7 +10361,7 @@ "node_modules/gulp-clean/node_modules/trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "integrity": "sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==", "dev": true, "engines": { "node": ">=0.10.0" @@ -11603,207 +10823,144 @@ } }, "node_modules/gulp-eslint": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-4.0.2.tgz", - "integrity": "sha512-fcFUQzFsN6dJ6KZlG+qPOEkqfcevRUXgztkYCvhNvJeSvOicC8ucutN4qR/ID8LmNZx9YPIkBzazTNnVvbh8wg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", + "integrity": "sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig==", "dev": true, "dependencies": { - "eslint": "^4.0.0", + "eslint": "^6.0.0", "fancy-log": "^1.3.2", - "plugin-error": "^1.0.0" - } - }, - "node_modules/gulp-eslint/node_modules/acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/gulp-eslint/node_modules/acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "dependencies": { - "acorn": "^3.0.4" - } - }, - "node_modules/gulp-eslint/node_modules/acorn-jsx/node_modules/acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/gulp-eslint/node_modules/ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "dependencies": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "node_modules/gulp-eslint/node_modules/ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true, - "peerDependencies": { - "ajv": "^5.0.0" + "plugin-error": "^1.0.1" } }, - "node_modules/gulp-eslint/node_modules/ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "node_modules/gulp-eslint/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/gulp-eslint/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "node_modules/gulp-eslint/node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true, "engines": { "node": ">=4" } }, - "node_modules/gulp-eslint/node_modules/chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "node_modules/gulp-eslint/node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "node_modules/gulp-eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "restore-cursor": "^2.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=4" + "node": ">=7.0.0" } }, - "node_modules/gulp-eslint/node_modules/cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "node_modules/gulp-eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "node_modules/gulp-eslint/node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "dependencies": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } }, - "node_modules/gulp-eslint/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/gulp-eslint/node_modules/cross-spawn/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true, - "dependencies": { - "ms": "^2.1.1" + "bin": { + "semver": "bin/semver" } }, - "node_modules/gulp-eslint/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } + "node_modules/gulp-eslint/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true }, "node_modules/gulp-eslint/node_modules/eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "dependencies": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", + "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", + "optionator": "^0.8.3", "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": ">=4" + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/gulp-eslint/node_modules/eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "node_modules/gulp-eslint/node_modules/eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "eslint-visitor-keys": "^1.1.0" }, "engines": { - "node": ">=4.0.0" + "node": ">=6" } }, "node_modules/gulp-eslint/node_modules/eslint-visitor-keys": { @@ -11815,126 +10972,163 @@ "node": ">=4" } }, - "node_modules/gulp-eslint/node_modules/espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "node_modules/gulp-eslint/node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "dependencies": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/gulp-eslint/node_modules/external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "node_modules/gulp-eslint/node_modules/eslint/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "dependencies": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" + "ansi-regex": "^4.1.0" }, "engines": { - "node": ">=0.12" + "node": ">=6" } }, - "node_modules/gulp-eslint/node_modules/fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "node_modules/gulp-eslint/node_modules/figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "node_modules/gulp-eslint/node_modules/espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "dependencies": { - "escape-string-regexp": "^1.0.5" + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" }, "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, "node_modules/gulp-eslint/node_modules/file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, "dependencies": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "flat-cache": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, "node_modules/gulp-eslint/node_modules/flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "dependencies": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/gulp-eslint/node_modules/ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "node_modules/gulp-eslint/node_modules/flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "node_modules/gulp-eslint/node_modules/globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "dependencies": { + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gulp-eslint/node_modules/inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", "dev": true, "dependencies": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", "through": "^2.3.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/gulp-eslint/node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/gulp-eslint/node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/gulp-eslint/node_modules/is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true, "engines": { "node": ">=4" } }, - "node_modules/gulp-eslint/node_modules/json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, "node_modules/gulp-eslint/node_modules/levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, "dependencies": { "prelude-ls": "~1.1.2", @@ -11944,55 +11138,18 @@ "node": ">= 0.8.0" } }, - "node_modules/gulp-eslint/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/gulp-eslint/node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/gulp-eslint/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "dependencies": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, - "node_modules/gulp-eslint/node_modules/mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "node_modules/gulp-eslint/node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "dependencies": { - "mimic-fn": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/gulp-eslint/node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -12010,35 +11167,31 @@ "node": ">= 0.8.0" } }, + "node_modules/gulp-eslint/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/gulp-eslint/node_modules/prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/gulp-eslint/node_modules/regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/gulp-eslint/node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true, - "dependencies": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - }, "engines": { - "node": ">=4" + "node": ">=6.5.0" } }, "node_modules/gulp-eslint/node_modules/rimraf": { @@ -12053,19 +11206,22 @@ "rimraf": "bin.js" } }, - "node_modules/gulp-eslint/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/gulp-eslint/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, - "bin": { - "semver": "bin/semver" + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" } }, "node_modules/gulp-eslint/node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, "dependencies": { "shebang-regex": "^1.0.0" @@ -12077,67 +11233,95 @@ "node_modules/gulp-eslint/node_modules/shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true, "engines": { "node": ">=0.10.0" } }, "node_modules/gulp-eslint/node_modules/slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=6" } }, - "node_modules/gulp-eslint/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "node_modules/gulp-eslint/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gulp-eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/gulp-eslint/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "node_modules/gulp-eslint/node_modules/table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "dependencies": { - "ansi-regex": "^3.0.0" + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/gulp-eslint/node_modules/table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "node_modules/gulp-eslint/node_modules/table/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "dependencies": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/gulp-eslint/node_modules/table/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" } }, "node_modules/gulp-eslint/node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, "dependencies": { "prelude-ls": "~1.1.2" @@ -12146,6 +11330,15 @@ "node": ">= 0.8.0" } }, + "node_modules/gulp-eslint/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/gulp-eslint/node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -12158,44 +11351,6 @@ "which": "bin/which" } }, - "node_modules/gulp-eslint/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "node_modules/gulp-footer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/gulp-footer/-/gulp-footer-2.1.0.tgz", - "integrity": "sha512-CK3nRBP3PG59XN2L1rDLkBHA7goYsW+tJuVQccLP9jq3mpBT2kuRq0ImgNjrUkDbF948aCVQH4J7uIEqiZ2MHA==", - "dev": true, - "dependencies": { - "lodash": "^4.17.21", - "map-stream": "^0.0.7" - } - }, - "node_modules/gulp-header": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-2.0.9.tgz", - "integrity": "sha512-LMGiBx+qH8giwrOuuZXSGvswcIUh0OiioNkUpLhNyvaC6/Ga8X6cfAeme2L5PqsbXMhL8o8b/OmVqIQdxprhcQ==", - "dev": true, - "dependencies": { - "concat-with-sourcemaps": "^1.1.0", - "lodash.template": "^4.5.0", - "map-stream": "0.0.7", - "through2": "^2.0.0" - } - }, - "node_modules/gulp-header/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, "node_modules/gulp-if": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz", @@ -12447,7 +11602,7 @@ "node_modules/gulp-util": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "integrity": "sha512-q5oWPc12lwSFS9h/4VIjG+1NuNDlJ48ywV2JKItY4Ycc/n1fXJeYPVQsfu5ZrhQi7FGSDBalwUCLar/GyHXKGw==", "deprecated": "gulp-util is deprecated - replace it, following the guidelines at https://medium.com/gulpjs/gulp-util-ca3b1f9f9ac5", "dev": true, "dependencies": { @@ -12546,7 +11701,7 @@ "node_modules/gulp-util/node_modules/lodash.template": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "integrity": "sha512-0B4Y53I0OgHUJkt+7RmlDFWKjVAI/YUpWNiL9GQz5ORDr4ttgfQGo+phBWKFLJbBdtOwgMuUkdOHOnPg45jKmQ==", "dev": true, "dependencies": { "lodash._basecopy": "^3.0.0", @@ -12720,15 +11875,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -12863,22 +12009,26 @@ } }, "node_modules/hast-util-is-element": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz", - "integrity": "sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.2.tgz", + "integrity": "sha512-thjnlGAnwP8ef/GSO1Q8BfVk2gundnc2peGQqEg2kUt/IqesiGg/5mSwN2fE7nLzy61pg88NG6xV+UrGOrx9EA==", "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-sanitize": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-3.0.2.tgz", - "integrity": "sha512-+2I0x2ZCAyiZOO/sb4yNLFmdwPBnyJ4PBkVTUMKMqBwYNA+lXSgOmoRXlJFazoyid9QPogRRKgKhVEodv181sA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.0.0.tgz", + "integrity": "sha512-pw56+69jq+QSr/coADNvWTmBPDy+XsmwaF5KnUys4/wM1jt/fZdl7GPxhXXXYdXnz3Gj3qMkbUCH2uKjvX0MgQ==", "dev": true, "dependencies": { - "xtend": "^4.0.0" + "@types/hast": "^2.0.0" }, "funding": { "type": "opencollective", @@ -12886,21 +12036,21 @@ } }, "node_modules/hast-util-to-html": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-7.1.3.tgz", - "integrity": "sha512-yk2+1p3EJTEE9ZEUkgHsUSVhIpCsL/bvT8E5GzmWc+N1Po5gBw+0F8bo7dpxXR0nu0bQVxVZGX2lBGF21CmeDw==", - "dev": true, - "dependencies": { - "ccount": "^1.0.0", - "comma-separated-tokens": "^1.0.0", - "hast-util-is-element": "^1.0.0", - "hast-util-whitespace": "^1.0.0", - "html-void-elements": "^1.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0", - "stringify-entities": "^3.0.1", - "unist-util-is": "^4.0.0", - "xtend": "^4.0.0" + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.3.tgz", + "integrity": "sha512-/D/E5ymdPYhHpPkuTHOUkSatxr4w1ZKrZsG0Zv/3C2SRVT0JFJG53VS45AMrBtYk0wp5A7ksEhiC8QaOZM95+A==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.2", + "unist-util-is": "^5.0.0" }, "funding": { "type": "opencollective", @@ -12908,9 +12058,9 @@ } }, "node_modules/hast-util-whitespace": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz", - "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz", + "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==", "dev": true, "funding": { "type": "opencollective", @@ -12927,12 +12077,12 @@ } }, "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", + "integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==", "dev": true, "engines": { - "node": "*" + "node": ">=12.0.0" } }, "node_modules/home-or-tmp": { @@ -12973,9 +12123,9 @@ "dev": true }, "node_modules/html-void-elements": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", - "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", "dev": true, "funding": { "type": "github", @@ -13138,15 +12288,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -13337,30 +12478,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dev": true, - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -13459,9 +12576,9 @@ } }, "node_modules/is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", "dependencies": { "has": "^1.0.3" }, @@ -13505,16 +12622,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", @@ -13634,16 +12741,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -13744,22 +12841,16 @@ "node": ">=0.10.0" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-plain-object": { @@ -13805,12 +12896,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, "node_modules/is-running": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", @@ -13836,12 +12921,12 @@ } }, "node_modules/is-ssh": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.3.tgz", - "integrity": "sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", "dev": true, "dependencies": { - "protocols": "^1.1.0" + "protocols": "^2.0.1" } }, "node_modules/is-stream": { @@ -13883,18 +12968,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", - "dev": true, - "dependencies": { - "text-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-typed-array": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz", @@ -14088,21 +13161,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -14249,13 +13307,13 @@ } }, "node_modules/jake": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", - "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "version": "10.8.5", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", + "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", "dev": true, "dependencies": { - "async": "0.9.x", - "chalk": "^2.4.2", + "async": "^3.2.3", + "chalk": "^4.0.2", "filelist": "^1.0.1", "minimatch": "^3.0.4" }, @@ -14263,15 +13321,76 @@ "jake": "bin/cli.js" }, "engines": { - "node": "*" + "node": ">=10" + } + }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jake/node_modules/async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", "dev": true }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jake/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jake/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jake/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-diff": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", @@ -14632,12 +13751,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dependencies": { - "minimist": "^1.2.5" - }, + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "bin": { "json5": "lib/cli.js" }, @@ -14657,31 +13773,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true, - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -14990,9 +14081,9 @@ } }, "node_modules/karma-mocha-reporter/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true, "engines": { "node": ">=4" @@ -15109,18 +14200,6 @@ "node": ">=0.10.0" } }, - "node_modules/karma/node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, "node_modules/karma/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -15166,6 +14245,15 @@ "node": ">=0.10.0" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/konan": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", @@ -15344,30 +14432,6 @@ "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", "dev": true }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -15377,20 +14441,6 @@ "node": ">=6.11.5" } }, - "node_modules/loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", @@ -15586,12 +14636,6 @@ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", "dev": true }, - "node_modules/lodash.ismatch": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", - "dev": true - }, "node_modules/lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", @@ -15757,9 +14801,9 @@ "dev": true }, "node_modules/longest-streak": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", - "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz", + "integrity": "sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==", "dev": true, "funding": { "type": "github", @@ -15885,18 +14929,6 @@ "node": ">=0.10.0" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/map-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", @@ -15916,13 +14948,10 @@ } }, "node_modules/markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.2.tgz", + "integrity": "sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==", "dev": true, - "dependencies": { - "repeat-string": "^1.0.0" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -15977,12 +15006,14 @@ } }, "node_modules/mdast-util-definitions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", - "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz", + "integrity": "sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==", "dev": true, "dependencies": { - "unist-util-visit": "^2.0.0" + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" }, "funding": { "type": "opencollective", @@ -15990,14 +15021,14 @@ } }, "node_modules/mdast-util-find-and-replace": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", - "integrity": "sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz", + "integrity": "sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==", "dev": true, "dependencies": { - "escape-string-regexp": "^4.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" }, "funding": { "type": "opencollective", @@ -16005,28 +15036,35 @@ } }, "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mdast-util-from-markdown": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", - "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", + "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", "dev": true, "dependencies": { "@types/mdast": "^3.0.0", - "mdast-util-to-string": "^2.0.0", - "micromark": "~2.11.0", - "parse-entities": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" }, "funding": { "type": "opencollective", @@ -16034,9 +15072,9 @@ } }, "node_modules/mdast-util-from-markdown/node_modules/mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", + "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", "dev": true, "funding": { "type": "opencollective", @@ -16044,16 +15082,18 @@ } }, "node_modules/mdast-util-gfm": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz", - "integrity": "sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.1.tgz", + "integrity": "sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==", "dev": true, "dependencies": { - "mdast-util-gfm-autolink-literal": "^0.1.0", - "mdast-util-gfm-strikethrough": "^0.2.0", - "mdast-util-gfm-table": "^0.1.0", - "mdast-util-gfm-task-list-item": "^0.1.0", - "mdast-util-to-markdown": "^0.6.1" + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" }, "funding": { "type": "opencollective", @@ -16061,14 +15101,30 @@ } }, "node_modules/mdast-util-gfm-autolink-literal": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz", - "integrity": "sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz", + "integrity": "sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz", + "integrity": "sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==", "dev": true, "dependencies": { - "ccount": "^1.0.0", - "mdast-util-find-and-replace": "^1.1.0", - "micromark": "^2.11.3" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" }, "funding": { "type": "opencollective", @@ -16076,12 +15132,13 @@ } }, "node_modules/mdast-util-gfm-strikethrough": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz", - "integrity": "sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.1.tgz", + "integrity": "sha512-zKJbEPe+JP6EUv0mZ0tQUyLQOC+FADt0bARldONot/nefuISkaZFlmVK4tU6JgfyZGrky02m/I6PmehgAgZgqg==", "dev": true, "dependencies": { - "mdast-util-to-markdown": "^0.6.0" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" }, "funding": { "type": "opencollective", @@ -16089,13 +15146,14 @@ } }, "node_modules/mdast-util-gfm-table": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz", - "integrity": "sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.4.tgz", + "integrity": "sha512-aEuoPwZyP4iIMkf2cLWXxx3EQ6Bmh2yKy9MVCg4i6Sd3cX80dcLEfXO/V4ul3pGH9czBK4kp+FAl+ZHmSUt9/w==", "dev": true, "dependencies": { - "markdown-table": "^2.0.0", - "mdast-util-to-markdown": "~0.6.0" + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" }, "funding": { "type": "opencollective", @@ -16103,12 +15161,13 @@ } }, "node_modules/mdast-util-gfm-task-list-item": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", - "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz", + "integrity": "sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==", "dev": true, "dependencies": { - "mdast-util-to-markdown": "~0.6.0" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" }, "funding": { "type": "opencollective", @@ -16125,19 +15184,22 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz", - "integrity": "sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ==", + "version": "12.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.1.tgz", + "integrity": "sha512-dyindR2P7qOqXO1hQirZeGtVbiX7xlNQbw7gGaAwN4A1dh4+X8xU/JyYmRoyB8Fu1uPXzp7mlL5QwW7k+knvgA==", "dev": true, "dependencies": { + "@types/hast": "^2.0.0", "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "mdast-util-definitions": "^4.0.0", + "@types/mdurl": "^1.0.0", + "mdast-util-definitions": "^5.0.0", "mdurl": "^1.0.0", - "unist-builder": "^2.0.0", - "unist-util-generated": "^1.0.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^2.0.0" + "micromark-util-sanitize-uri": "^1.0.0", + "trim-lines": "^3.0.0", + "unist-builder": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" }, "funding": { "type": "opencollective", @@ -16145,17 +15207,18 @@ } }, "node_modules/mdast-util-to-markdown": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", - "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.3.0.tgz", + "integrity": "sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==", "dev": true, "dependencies": { + "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", - "longest-streak": "^2.0.0", - "mdast-util-to-string": "^2.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.0.0", - "zwitch": "^1.0.0" + "longest-streak": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", @@ -16163,9 +15226,9 @@ } }, "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", + "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", "dev": true, "funding": { "type": "opencollective", @@ -16183,44 +15246,68 @@ } }, "node_modules/mdast-util-toc": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-5.1.0.tgz", - "integrity": "sha512-csimbRIVkiqc+PpFeKDGQ/Ck2N4f9FYH3zzBMMJzcxoKL8m+cM0n94xXm0I9eaxHnKdY9n145SGTdyJC7i273g==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.0.tgz", + "integrity": "sha512-0PuqZELXZl4ms1sF7Lqigrqik4Ll3UhbI+jdTrfw7pZ9QPawgl7LD4GQ8MkU7bT/EwiVqChNTbifa2jLLKo76A==", "dev": true, "dependencies": { - "@types/mdast": "^3.0.3", - "@types/unist": "^2.0.3", - "extend": "^3.0.2", - "github-slugger": "^1.2.1", - "mdast-util-to-string": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit": "^2.0.0" + "@types/extend": "^3.0.0", + "@types/github-slugger": "^1.0.0", + "@types/mdast": "^3.0.0", + "extend": "^3.0.0", + "github-slugger": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "unist-util-is": "^5.0.0", + "unist-util-visit": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-toc/node_modules/github-slugger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", - "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==", - "dev": true - }, "node_modules/mdast-util-toc/node_modules/mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", + "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-toc/node_modules/unist-util-visit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz", + "integrity": "sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-toc/node_modules/unist-util-visit-parents": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz", + "integrity": "sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", "dev": true }, "node_modules/media-typer": { @@ -16260,300 +15347,427 @@ "node": ">=4.3.0 <5.0.0 || >=5.10" } }, - "node_modules/meow": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", - "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/meow/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/micromark": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.0.10.tgz", + "integrity": "sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", + "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" } }, - "node_modules/meow/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/micromark-extension-gfm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz", + "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/meow/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==", "dev": true, "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, - "engines": { - "node": ">=10" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/meow/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/micromark-extension-gfm-footnote": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.4.tgz", + "integrity": "sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==", "dev": true, "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/meow/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz", + "integrity": "sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/meow/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/micromark-extension-gfm-table": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz", + "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/meow/node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz", + "integrity": "sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==", "dev": true, "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" + "micromark-util-types": "^1.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/meow/node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz", + "integrity": "sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==", "dev": true, "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/micromark-factory-destination": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", + "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", "dev": true, - "engines": { - "node": ">=8" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/meow/node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/meow/node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "node_modules/micromark-factory-label": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", + "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, - "node_modules/meow/node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "node_modules/micromark-factory-space": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", + "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", "dev": true, - "bin": { - "semver": "bin/semver" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/meow/node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "node_modules/micromark-factory-title": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", + "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", "dev": true, - "engines": { - "node": ">=8" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, - "node_modules/meow/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "node_modules/micromark-factory-whitespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", + "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "node_modules/micromark-util-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", + "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/meow/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "node_modules/micromark-util-chunked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", + "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", "dev": true, - "engines": { - "node": ">=10" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "node_modules/micromark-util-classify-character": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", + "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "source-map": "^0.6.1" + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/merge-source-map/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/micromark-util-combine-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", + "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", "dev": true, - "engines": { - "node": ">=0.10.0" + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", + "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/micromark": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", - "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "node_modules/micromark-util-decode-string": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", + "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", "dev": true, "funding": [ { @@ -16566,89 +15780,156 @@ } ], "dependencies": { - "debug": "^4.0.0", - "parse-entities": "^2.0.0" + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/micromark-extension-gfm": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", - "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", + "node_modules/micromark-util-encode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", + "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", + "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", + "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "micromark": "~2.11.0", - "micromark-extension-gfm-autolink-literal": "~0.5.0", - "micromark-extension-gfm-strikethrough": "~0.6.5", - "micromark-extension-gfm-table": "~0.4.0", - "micromark-extension-gfm-tagfilter": "~0.3.0", - "micromark-extension-gfm-task-list-item": "~0.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", - "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", + "node_modules/micromark-util-resolve-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", + "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "micromark": "~2.11.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-types": "^1.0.0" } }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", - "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", + "node_modules/micromark-util-sanitize-uri": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz", + "integrity": "sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" } }, - "node_modules/micromark-extension-gfm-table": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", - "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", + "node_modules/micromark-util-subtokenize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", + "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", - "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", + "node_modules/micromark-util-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", + "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", - "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", + "node_modules/micromark-util-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", + "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", "dev": true, - "dependencies": { - "micromark": "~2.11.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] }, "node_modules/micromatch": { "version": "3.1.10", @@ -16814,15 +16095,6 @@ "node": ">=4" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -16838,30 +16110,8 @@ "node_modules/minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/minimist-options/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -16979,13 +16229,13 @@ "node_modules/mocha/node_modules/minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", "dev": true }, "node_modules/mocha/node_modules/mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", "dev": true, "dependencies": { @@ -17013,101 +16263,6 @@ "node": ">=4" } }, - "node_modules/modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/module-deps-sortable": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/module-deps-sortable/-/module-deps-sortable-5.0.3.tgz", - "integrity": "sha512-eiyIZj/A0dj1o4ywXWqicazUL3l0HP3TydUR6xF0X3xh3LGBMLqW8a9aFe6MuNH4mxNMk53QKBHM6LOPR8kSgw==", - "dev": true, - "dependencies": { - "browser-resolve": "^1.7.0", - "cached-path-relative": "^1.0.0", - "concat-stream": "~1.5.0", - "defined": "^1.0.0", - "detective": "^5.2.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "JSONStream": "^1.0.3", - "konan": "^2.1.1", - "readable-stream": "^2.0.2", - "resolve": "^1.1.3", - "standard-version": "^9.0.0", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" - }, - "bin": { - "module-deps": "bin/cmd.js" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/module-deps-sortable/node_modules/concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "~2.0.0", - "typedarray": "~0.0.5" - } - }, - "node_modules/module-deps-sortable/node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/module-deps-sortable/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/module-deps-sortable/node_modules/process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "node_modules/module-deps-sortable/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "node_modules/module-deps-sortable/node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -17148,6 +16303,15 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/mrmime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", @@ -17227,9 +16391,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true, "optional": true, "bin": { @@ -17379,9 +16543,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, "node_modules/nopt": { "version": "3.0.6", @@ -17470,15 +16634,6 @@ "node": ">=4" } }, - "node_modules/null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -18031,24 +17186,6 @@ "node": ">=6" } }, - "node_modules/parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dev": true, - "dependencies": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -18064,16 +17201,21 @@ } }, "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "dependencies": { + "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/parse-ms": { @@ -18104,27 +17246,24 @@ } }, "node_modules/parse-path": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.3.tgz", - "integrity": "sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-5.0.0.tgz", + "integrity": "sha512-qOpH55/+ZJ4jUu/oLO+ifUKjFPNZGfnPJtzvGzKN/4oLMil5m9OH4VpOj6++9/ytJcfks4kzH2hhi87GL/OU9A==", "dev": true, "dependencies": { - "is-ssh": "^1.3.0", - "protocols": "^1.4.0", - "qs": "^6.9.4", - "query-string": "^6.13.8" + "protocols": "^2.0.0" } }, "node_modules/parse-url": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-6.0.0.tgz", - "integrity": "sha512-cYyojeX7yIIwuJzledIHeLUBVJ6COVLeT4eF+2P6aKVzwvgKQPndCBv3+yQ7pcWjqToYwaligxzSYNNmGoMAvw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", + "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", "dev": true, "dependencies": { - "is-ssh": "^1.3.0", + "is-ssh": "^1.4.0", "normalize-url": "^6.1.0", - "parse-path": "^4.0.0", - "protocols": "^1.4.0" + "parse-path": "^5.0.0", + "protocols": "^2.0.1" } }, "node_modules/parseurl": { @@ -18168,12 +17307,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -18214,27 +17347,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-type/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -18283,12 +17395,12 @@ } }, "node_modules/pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-6.1.0.tgz", + "integrity": "sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -18424,15 +17536,6 @@ "node": ">=0.10.0" } }, - "node_modules/pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -18443,22 +17546,28 @@ } }, "node_modules/postcss": { - "version": "8.4.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz", - "integrity": "sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==", + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], "optional": true, "dependencies": { - "nanoid": "^3.3.1", + "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" } }, "node_modules/prelude-ls": { @@ -18557,22 +17666,19 @@ } }, "node_modules/property-information": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", - "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", + "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", "dev": true, - "dependencies": { - "xtend": "^4.0.0" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/protocols": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", - "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", "dev": true }, "node_modules/proxy-addr": { @@ -18789,24 +17895,6 @@ "integrity": "sha512-bK0/0cCI+R8ZmOF1QjT7HupDUYCxbf/9TJgAmSXQxZpftXmTAeil9DRoCnTDkWbvOyZzhcMBwKpptWcdkGFIMg==", "dev": true }, - "node_modules/query-string": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", - "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", - "dev": true, - "dependencies": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -18873,91 +17961,186 @@ "dev": true }, "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", + "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", "dev": true, "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", + "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", "dev": true, "dependencies": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" + "find-up": "^6.3.0", + "read-pkg": "^7.1.0", + "type-fest": "^2.5.0" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "dev": true, "dependencies": { - "locate-path": "^3.0.0" + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.1.1.tgz", + "integrity": "sha512-vJXaRMJgRVD3+cUZs3Mncj2mxpt5mP0EmNOsxRSZRMlbqjvxzDEOIUWXGmavo0ZC9+tNZCBLQ66reA11nbpHZg==", "dev": true, "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^1.0.0" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "dev": true, "dependencies": { - "p-limit": "^2.0.0" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/readable-stream": { @@ -19038,19 +18221,6 @@ "node": "*" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -19070,7 +18240,8 @@ "node_modules/regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true }, "node_modules/regenerator-transform": { "version": "0.14.5", @@ -19162,14 +18333,15 @@ } }, "node_modules/remark": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", - "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.2.tgz", + "integrity": "sha512-A3ARm2V4BgiRXaUo5K0dRvJ1lbogrbXnhkJRmD0yw092/Yl0kOCZt1k9ZeElEwkZsWGsMumz6qL5MfNJH9nOBA==", "dev": true, "dependencies": { - "remark-parse": "^9.0.0", - "remark-stringify": "^9.0.0", - "unified": "^9.1.0" + "@types/mdast": "^3.0.0", + "remark-parse": "^10.0.0", + "remark-stringify": "^10.0.0", + "unified": "^10.0.0" }, "funding": { "type": "opencollective", @@ -19177,13 +18349,15 @@ } }, "node_modules/remark-gfm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz", - "integrity": "sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", "dev": true, "dependencies": { - "mdast-util-gfm": "^0.1.0", - "micromark-extension-gfm": "^0.3.0" + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" }, "funding": { "type": "opencollective", @@ -19191,14 +18365,16 @@ } }, "node_modules/remark-html": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-13.0.2.tgz", - "integrity": "sha512-LhSRQ+3RKdBqB/RGesFWkNNfkGqprDUCwjq54SylfFeNyZby5kqOG8Dn/vYsRoM8htab6EWxFXCY6XIZvMoRiQ==", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.1.tgz", + "integrity": "sha512-7ta5UPRqj8nP0GhGMYUAghZ/DRno7dgq7alcW90A7+9pgJsXzGJlFgwF8HOP1b1tMgT3WwbeANN+CaTimMfyNQ==", "dev": true, "dependencies": { - "hast-util-sanitize": "^3.0.0", - "hast-util-to-html": "^7.0.0", - "mdast-util-to-hast": "^10.0.0" + "@types/mdast": "^3.0.0", + "hast-util-sanitize": "^4.0.0", + "hast-util-to-html": "^8.0.0", + "mdast-util-to-hast": "^12.0.0", + "unified": "^10.0.0" }, "funding": { "type": "opencollective", @@ -19206,12 +18382,14 @@ } }, "node_modules/remark-parse": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", - "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", + "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", "dev": true, "dependencies": { - "mdast-util-from-markdown": "^0.8.0" + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" }, "funding": { "type": "opencollective", @@ -19219,12 +18397,14 @@ } }, "node_modules/remark-reference-links": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-5.0.0.tgz", - "integrity": "sha512-oSIo6lfDyG/1yYl2jPZNXmD9dgyPxp07mSd7snJagVMsDU6NRlD8i54MwHWUgMoOHTs8lIKPkwaUok/tbr5syQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-6.0.1.tgz", + "integrity": "sha512-34wY2C6HXSuKVTRtyJJwefkUD8zBOZOSHFZ4aSTnU2F656gr9WeuQ2dL6IJDK3NPd2F6xKF2t4XXcQY9MygAXg==", "dev": true, "dependencies": { - "unist-util-visit": "^2.0.0" + "@types/mdast": "^3.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" }, "funding": { "type": "opencollective", @@ -19232,12 +18412,14 @@ } }, "node_modules/remark-stringify": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", - "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.2.tgz", + "integrity": "sha512-6wV3pvbPvHkbNnWB0wdDvVFHOe1hBRAx1Q/5g/EpH4RppAII6J8Gnwe7VbHuXaoKIF6LAg6ExTel/+kNqSQ7lw==", "dev": true, "dependencies": { - "mdast-util-to-markdown": "^0.6.0" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.0.0", + "unified": "^10.0.0" }, "funding": { "type": "opencollective", @@ -19245,13 +18427,14 @@ } }, "node_modules/remark-toc": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-7.2.0.tgz", - "integrity": "sha512-ppHepvpbg7j5kPFmU5rzDC4k2GTcPDvWcxXyr/7BZzO1cBSPk0stKtEJdsgAyw2WHKPGxadcHIZRjb2/sHxjkg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-8.0.1.tgz", + "integrity": "sha512-7he2VOm/cy13zilnOTZcyAoyoolV26ULlon6XyCFU+vG54Z/LWJnwphj/xKIDLOt66QmJUgTyUvLVHi2aAElyg==", "dev": true, "dependencies": { - "@types/unist": "^2.0.3", - "mdast-util-toc": "^5.0.0" + "@types/mdast": "^3.0.0", + "mdast-util-toc": "^6.0.0", + "unified": "^10.0.0" }, "funding": { "type": "opencollective", @@ -19430,34 +18613,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "dependencies": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-uncached/node_modules/resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -19465,11 +18620,11 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", "dependencies": { - "is-core-module": "^2.8.1", + "is-core-module": "^2.9.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -19609,21 +18764,6 @@ "node": ">=0.12.0" } }, - "node_modules/rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "node_modules/rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, - "dependencies": { - "rx-lite": "*" - } - }, "node_modules/rxjs": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", @@ -19639,6 +18779,18 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -20363,10 +19515,17 @@ "node": ">=10.0.0" } }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -20426,9 +19585,9 @@ "optional": true }, "node_modules/space-separated-tokens": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", - "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", + "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", "dev": true, "funding": { "type": "github", @@ -20488,15 +19647,6 @@ "node": "*" } }, - "node_modules/split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -20579,156 +19729,6 @@ "node": ">=8" } }, - "node_modules/standard-version": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.3.2.tgz", - "integrity": "sha512-u1rfKP4o4ew7Yjbfycv80aNMN2feTiqseAhUhrrx2XtdQGmu7gucpziXe68Z4YfHVqlxVEzo4aUA0Iu3VQOTgQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "conventional-changelog": "3.1.24", - "conventional-changelog-config-spec": "2.1.0", - "conventional-changelog-conventionalcommits": "4.6.1", - "conventional-recommended-bump": "6.1.0", - "detect-indent": "^6.0.0", - "detect-newline": "^3.1.0", - "dotgitignore": "^2.1.0", - "figures": "^3.1.0", - "find-up": "^5.0.0", - "fs-access": "^1.0.1", - "git-semver-tags": "^4.0.0", - "semver": "^7.1.1", - "stringify-package": "^1.0.1", - "yargs": "^16.0.0" - }, - "bin": { - "standard-version": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/standard-version/node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/standard-version/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/standard-version/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/standard-version/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/standard-version/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/standard-version/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/standard-version/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/standard-version/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/standard-version/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -20830,51 +19830,6 @@ "node": ">= 0.6" } }, - "node_modules/stream-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/stream-array/-/stream-array-1.1.2.tgz", - "integrity": "sha1-nl9zRfITfDDuO0mLkRToC1K7frU=", - "dev": true, - "dependencies": { - "readable-stream": "~2.1.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-array/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/stream-array/node_modules/process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "node_modules/stream-array/node_modules/readable-stream": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", - "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=", - "dev": true, - "dependencies": { - "buffer-shims": "^1.0.0", - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/stream-array/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, "node_modules/stream-buffers": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", @@ -20893,16 +19848,6 @@ "duplexer": "~0.1.1" } }, - "node_modules/stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "dependencies": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, "node_modules/stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -20929,15 +19874,6 @@ "node": ">=8.0" } }, - "node_modules/strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -21000,26 +19936,19 @@ } }, "node_modules/stringify-entities": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.1.0.tgz", - "integrity": "sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", "dev": true, "dependencies": { - "character-entities-html4": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "xtend": "^4.0.0" + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/stringify-package": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", - "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", - "dev": true - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -21059,36 +19988,6 @@ "node": ">=0.10.0" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "dev": true, - "dependencies": { - "minimist": "^1.1.0" - } - }, "node_modules/suffix": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/suffix/-/suffix-0.1.1.tgz", @@ -21270,14 +20169,14 @@ } }, "node_modules/terser": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.0.tgz", - "integrity": "sha512-R3AUhNBGWiFc77HXag+1fXpAxTAFRQTJemlJKjAgD9r8xXTpjNKqIXwHM/o7Rh+O0kUJtS3WQVdBeMKFk5sw9A==", + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "dev": true, "dependencies": { + "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.7.2", "source-map-support": "~0.5.20" }, "bin": { @@ -21391,22 +20290,18 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "node_modules/terser/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/text-extensions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", - "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/text-table": { @@ -21524,15 +20419,15 @@ } }, "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", "dev": true, "dependencies": { - "os-tmpdir": "~1.0.2" + "rimraf": "^3.0.0" }, "engines": { - "node": ">=0.6.0" + "node": ">=8.17.0" } }, "node_modules/to-absolute-glob": { @@ -21689,13 +20584,14 @@ "node": "*" } }, - "node_modules/trim-newlines": { + "node_modules/trim-lines": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "dev": true, - "engines": { - "node": ">=8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/trim-right": { @@ -21708,9 +20604,9 @@ } }, "node_modules/trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", "dev": true, "funding": { "type": "github", @@ -21823,9 +20719,9 @@ "dev": true }, "node_modules/typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz", + "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==", "dev": true, "peer": true, "bin": { @@ -21996,32 +20892,24 @@ } }, "node_modules/unified": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", - "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", "dev": true, "dependencies": { - "bail": "^1.0.0", + "@types/unist": "^2.0.0", + "bail": "^2.0.0", "extend": "^3.0.0", "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/unified/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -22057,19 +20945,22 @@ } }, "node_modules/unist-builder": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", - "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz", + "integrity": "sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==", "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-generated": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", - "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz", + "integrity": "sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==", "dev": true, "funding": { "type": "opencollective", @@ -22077,9 +20968,9 @@ } }, "node_modules/unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz", + "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==", "dev": true, "funding": { "type": "opencollective", @@ -22087,22 +20978,25 @@ } }, "node_modules/unist-util-position": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", - "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz", + "integrity": "sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==", "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", + "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", "dev": true, "dependencies": { - "@types/unist": "^2.0.2" + "@types/unist": "^2.0.0" }, "funding": { "type": "opencollective", @@ -22110,14 +21004,14 @@ } }, "node_modules/unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz", + "integrity": "sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" }, "funding": { "type": "opencollective", @@ -22125,13 +21019,13 @@ } }, "node_modules/unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz", + "integrity": "sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" + "unist-util-is": "^5.0.0" }, "funding": { "type": "opencollective", @@ -22236,6 +21130,31 @@ "yarn": "*" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz", + "integrity": "sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -22325,6 +21244,24 @@ "uuid": "bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -22391,15 +21328,15 @@ "dev": true }, "node_modules/vfile": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", - "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.4.tgz", + "integrity": "sha512-KI+7cnst03KbEyN1+JE504zF5bJBZa+J+CrevLeyIMq0aPU681I2rQ5p4PlnQ6exFtWiUrg26QUdFMnAKR6PIw==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" }, "funding": { "type": "opencollective", @@ -22407,13 +21344,13 @@ } }, "node_modules/vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", + "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", "dev": true, "dependencies": { "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" + "unist-util-stringify-position": "^3.0.0" }, "funding": { "type": "opencollective", @@ -22421,59 +21358,100 @@ } }, "node_modules/vfile-reporter": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-6.0.2.tgz", - "integrity": "sha512-GN2bH2gs4eLnw/4jPSgfBjo+XCuvnX9elHICJZjVD4+NM0nsUrMTvdjGY5Sc/XG69XVTgLwj7hknQVc6M9FukA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.4.tgz", + "integrity": "sha512-4cWalUnLrEnbeUQ+hARG5YZtaHieVK3Jp4iG5HslttkVl+MHunSGNAIrODOTLbtjWsNZJRMCkL66AhvZAYuJ9A==", "dev": true, "dependencies": { - "repeat-string": "^1.5.0", - "string-width": "^4.0.0", - "supports-color": "^6.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-sort": "^2.1.2", - "vfile-statistics": "^1.1.0" + "@types/supports-color": "^8.0.0", + "string-width": "^5.0.0", + "supports-color": "^9.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-sort": "^3.0.0", + "vfile-statistics": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/vfile-reporter/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "node_modules/vfile-reporter/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/vfile-reporter/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "node_modules/vfile-reporter/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "dependencies": { - "has-flag": "^3.0.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vfile-reporter/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/vfile-reporter/node_modules/supports-color": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", + "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/vfile-sort": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-2.2.2.tgz", - "integrity": "sha512-tAyUqD2R1l/7Rn7ixdGkhXLD3zsg+XLAeUDUhXearjfIcpL1Hcsj5hHpCoy/gvfK/Ws61+e972fm0F7up7hfYA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.0.tgz", + "integrity": "sha512-fJNctnuMi3l4ikTVcKpxTbzHeCgvDhnI44amA3NVDvA6rTC6oKCFpCVyT5n2fFMr3ebfr+WVQZedOCd73rzSxg==", "dev": true, + "dependencies": { + "vfile-message": "^3.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/vfile-statistics": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-1.1.4.tgz", - "integrity": "sha512-lXhElVO0Rq3frgPvFBwahmed3X03vjPF8OcjKMy8+F1xU/3Q3QU3tKEDp743SFtb74PdF0UWpxPvtOP0GCLheA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.0.tgz", + "integrity": "sha512-foOWtcnJhKN9M2+20AOTlWi2dxNfAoeNIoxD5GXcO182UJyId4QrXa41fWrgcfV3FWTjdEDy3I4cpLVcQscIMA==", "dev": true, + "dependencies": { + "vfile-message": "^3.0.0" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" @@ -22592,14 +21570,24 @@ } }, "node_modules/vue-template-compiler": { - "version": "2.6.14", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", - "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz", + "integrity": "sha512-QO+8R9YRq1Gudm8ZMdo/lImZLJVUIAM8c07Vp84ojdDAf8HmPJc7XB556PcXV218k2AkKznsRz6xB5uOjAC4EQ==", "dev": true, "optional": true, "dependencies": { "de-indent": "^1.0.2", - "he": "^1.1.0" + "he": "^1.2.0" + } + }, + "node_modules/vue-template-compiler/node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "optional": true, + "bin": { + "he": "bin/he" } }, "node_modules/walk": { @@ -22901,6 +21889,44 @@ } } }, + "node_modules/webpack-manifest-plugin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.0.tgz", + "integrity": "sha512-8RQfMAdc5Uw3QbCQ/CBV/AXqOR8mt03B6GJmRbhWopE8GzRfEpn+k0ZuWywxW+5QZsffhmFDY1J6ohqJo+eMuw==", + "dev": true, + "dependencies": { + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "webpack": "^5.47.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", + "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "dev": true, + "dependencies": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/webpack-merge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", @@ -23084,12 +22110,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, "node_modules/which-typed-array": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", @@ -23188,24 +22208,24 @@ "dev": true }, "node_modules/write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, "dependencies": { "mkdirp": "^0.5.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, "node_modules/write/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "dependencies": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" @@ -23481,9 +22501,9 @@ } }, "node_modules/zwitch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", - "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz", + "integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==", "dev": true, "funding": { "type": "github", @@ -23507,37 +22527,37 @@ } }, "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", "requires": { - "@babel/highlight": "^7.16.7" + "@babel/highlight": "^7.18.6" } }, "@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.0.tgz", + "integrity": "sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw==" }, "@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz", + "integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==", "requires": { "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.17.2", - "@babel/parser": "^7.17.3", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.0", + "@babel/helper-compilation-targets": "^7.19.0", + "@babel/helper-module-transforms": "^7.19.0", + "@babel/helpers": "^7.19.0", + "@babel/parser": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", + "json5": "^2.2.1", "semver": "^6.3.0" } }, @@ -23553,13 +22573,13 @@ } }, "@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", + "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", "requires": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.19.0", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" } }, "@babel/helper-annotate-as-pure": { @@ -23580,13 +22600,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz", + "integrity": "sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==", "requires": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", + "@babel/compat-data": "^7.19.0", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", "semver": "^6.3.0" } }, @@ -23629,12 +22649,9 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "requires": { - "@babel/types": "^7.16.7" - } + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" }, "@babel/helper-explode-assignable-expression": { "version": "7.16.7", @@ -23645,29 +22662,20 @@ } }, "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "requires": { - "@babel/types": "^7.16.7" + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" } }, "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-member-expression-to-functions": { @@ -23679,26 +22687,26 @@ } }, "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-module-transforms": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", - "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", + "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0" + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" } }, "@babel/helper-optimise-call-expression": { @@ -23710,9 +22718,9 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz", + "integrity": "sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==" }, "@babel/helper-remap-async-to-generator": { "version": "7.16.8", @@ -23737,11 +22745,11 @@ } }, "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -23753,22 +22761,27 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.18.6" } }, + "@babel/helper-string-parser": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", + "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==" + }, "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==" }, "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==" }, "@babel/helper-wrap-function": { "version": "7.16.8", @@ -23782,29 +22795,29 @@ } }, "@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", + "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", "requires": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.0", - "@babel/types": "^7.17.0" + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.19.0", + "@babel/types": "^7.19.0" } }, "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", "requires": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-validator-identifier": "^7.18.6", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==" + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", + "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.16.7", @@ -24303,6 +23316,19 @@ "@babel/helper-plugin-utils": "^7.16.7" } }, + "@babel/plugin-transform-runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.9.tgz", + "integrity": "sha512-wS8uJwBt7/b/mzE13ktsJdmS4JP/j7PQSaADtnb4I2wL0zK51MQ0pmF8/Jy0wUIS96fr+fXT6S/ifiPXnvrlSg==", + "requires": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.9", + "babel-plugin-polyfill-corejs2": "^0.3.1", + "babel-plugin-polyfill-corejs3": "^0.5.2", + "babel-plugin-polyfill-regenerator": "^0.3.1", + "semver": "^6.3.0" + } + }, "@babel/plugin-transform-shorthand-properties": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", @@ -24455,9 +23481,9 @@ } }, "@babel/runtime": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", - "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", "requires": { "regenerator-runtime": "^0.13.4" }, @@ -24470,38 +23496,39 @@ } }, "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", + "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.10", + "@babel/types": "^7.18.10" } }, "@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", - "@babel/types": "^7.17.0", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz", + "integrity": "sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==", + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.19.0", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.19.0", + "@babel/types": "^7.19.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", + "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", "requires": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", "to-fast-properties": "^2.0.0" } }, @@ -24664,11 +23691,69 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "@hutson/parse-repository-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", - "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", - "dev": true + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } + } }, "@istanbuljs/schema": { "version": "0.1.3", @@ -24734,38 +23819,50 @@ } } }, + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "@jridgewell/resolve-uri": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==" }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "@jridgewell/sourcemap-codec": { "version": "1.4.11", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==" }, "@jridgewell/trace-mapping": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz", - "integrity": "sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==", + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "@jsdevtools/coverage-istanbul-loader": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", - "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", - "dev": true, - "requires": { - "convert-source-map": "^1.7.0", - "istanbul-lib-instrument": "^4.0.3", - "loader-utils": "^2.0.0", - "merge-source-map": "^1.1.0", - "schema-utils": "^2.7.0" - } - }, "@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -24864,6 +23961,15 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", "dev": true }, + "@types/debug": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "dev": true, + "requires": { + "@types/ms": "*" + } + }, "@types/diff": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.0.2.tgz", @@ -24914,6 +24020,12 @@ "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", "dev": true }, + "@types/extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.1.tgz", + "integrity": "sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==", + "dev": true + }, "@types/fibers": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/fibers/-/fibers-3.1.1.tgz", @@ -24929,6 +24041,21 @@ "@types/node": "*" } }, + "@types/github-slugger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz", + "integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==", + "dev": true + }, + "@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dev": true, + "requires": { + "@types/unist": "*" + } + }, "@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", @@ -25032,10 +24159,10 @@ "@types/unist": "*" } }, - "@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", "dev": true }, "@types/mocha": { @@ -25044,6 +24171,12 @@ "integrity": "sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==", "dev": true }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, "@types/node": { "version": "17.0.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", @@ -25185,14 +24318,14 @@ "dev": true }, "@vue/compiler-core": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.31.tgz", - "integrity": "sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.38.tgz", + "integrity": "sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==", "dev": true, "optional": true, "requires": { "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.31", + "@vue/shared": "3.2.38", "estree-walker": "^2.0.2", "source-map": "^0.6.1" }, @@ -25207,29 +24340,29 @@ } }, "@vue/compiler-dom": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz", - "integrity": "sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz", + "integrity": "sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==", "dev": true, "optional": true, "requires": { - "@vue/compiler-core": "3.2.31", - "@vue/shared": "3.2.31" + "@vue/compiler-core": "3.2.38", + "@vue/shared": "3.2.38" } }, "@vue/compiler-sfc": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz", - "integrity": "sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz", + "integrity": "sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==", "dev": true, "optional": true, "requires": { "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.31", - "@vue/compiler-dom": "3.2.31", - "@vue/compiler-ssr": "3.2.31", - "@vue/reactivity-transform": "3.2.31", - "@vue/shared": "3.2.31", + "@vue/compiler-core": "3.2.38", + "@vue/compiler-dom": "3.2.38", + "@vue/compiler-ssr": "3.2.38", + "@vue/reactivity-transform": "3.2.38", + "@vue/shared": "3.2.38", "estree-walker": "^2.0.2", "magic-string": "^0.25.7", "postcss": "^8.1.10", @@ -25246,34 +24379,34 @@ } }, "@vue/compiler-ssr": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz", - "integrity": "sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz", + "integrity": "sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==", "dev": true, "optional": true, "requires": { - "@vue/compiler-dom": "3.2.31", - "@vue/shared": "3.2.31" + "@vue/compiler-dom": "3.2.38", + "@vue/shared": "3.2.38" } }, "@vue/reactivity-transform": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz", - "integrity": "sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz", + "integrity": "sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==", "dev": true, "optional": true, "requires": { "@babel/parser": "^7.16.4", - "@vue/compiler-core": "3.2.31", - "@vue/shared": "3.2.31", + "@vue/compiler-core": "3.2.38", + "@vue/shared": "3.2.38", "estree-walker": "^2.0.2", "magic-string": "^0.25.7" } }, "@vue/shared": { - "version": "3.2.31", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.31.tgz", - "integrity": "sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==", + "version": "3.2.38", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.38.tgz", + "integrity": "sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg==", "dev": true, "optional": true }, @@ -26117,29 +25250,6 @@ "dev": true, "requires": {} }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "dev": true, - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "add-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", - "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", - "dev": true - }, "agent-base": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", @@ -26189,12 +25299,6 @@ "ansi-wrap": "0.1.0" } }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -26372,12 +25476,6 @@ "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", "dev": true }, - "array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", - "dev": true - }, "array-includes": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", @@ -26466,12 +25564,6 @@ "es-abstract": "^1.19.0" } }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, "asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -26795,6 +25887,34 @@ "object.assign": "^4.1.0" } }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-instrument": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", + "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + } + } + }, "babel-plugin-polyfill-corejs2": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", @@ -26822,14 +25942,6 @@ "@babel/helper-define-polyfill-provider": "^0.3.1" } }, - "babel-plugin-transform-object-assign": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.22.0.tgz", - "integrity": "sha1-+Z0vZvGgsNSY40bFNZaEdAyqILo=", - "requires": { - "babel-runtime": "^6.22.0" - } - }, "babel-register": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", @@ -26875,6 +25987,7 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -26883,7 +25996,8 @@ "core-js": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true } } }, @@ -26960,13 +26074,6 @@ } } }, - "babelify": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", - "integrity": "sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==", - "dev": true, - "requires": {} - }, "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", @@ -26991,9 +26098,9 @@ } }, "bail": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", - "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "dev": true }, "balanced-match": { @@ -27231,23 +26338,6 @@ "fill-range": "^7.0.1" } }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -27255,15 +26345,14 @@ "dev": true }, "browserslist": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.0.tgz", - "integrity": "sha512-bnpOoa+DownbciXj0jVGENf8VYQnE2LNWomhYuCsMmmx9Jd9lwq0WXODuwpSsp8AVdKM2/HorrzxAfbKvWTByQ==", + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", "requires": { - "caniuse-lite": "^1.0.30001313", - "electron-to-chromium": "^1.4.76", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" } }, "browserstack": { @@ -27391,12 +26480,6 @@ "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", "dev": true }, - "buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", - "dev": true - }, "buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -27630,12 +26713,6 @@ } } }, - "cached-path-relative": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz", - "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==", - "dev": true - }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -27645,23 +26722,6 @@ "get-intrinsic": "^1.0.2" } }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - }, - "dependencies": { - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - } - } - }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -27674,29 +26734,10 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "dependencies": { - "quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true - } - } - }, "caniuse-lite": { - "version": "1.0.30001320", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz", - "integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==" + "version": "1.0.30001390", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz", + "integrity": "sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g==" }, "caseless": { "version": "0.12.0", @@ -27705,9 +26746,9 @@ "dev": true }, "ccount": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", - "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "dev": true }, "chai": { @@ -27760,27 +26801,21 @@ } }, "character-entities": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", - "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "dev": true }, "character-entities-html4": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", - "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "dev": true }, "character-entities-legacy": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", - "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", - "dev": true - }, - "character-reference-invalid": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", - "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "dev": true }, "chardet": { @@ -27843,12 +26878,6 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -27999,12 +27028,6 @@ "readable-stream": "^2.3.5" } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -28067,9 +27090,9 @@ } }, "comma-separated-tokens": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", - "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz", + "integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==", "dev": true }, "commander": { @@ -28084,16 +27107,6 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "compare-func": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", - "dev": true, - "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^5.1.0" - } - }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -28221,313 +27234,6 @@ "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", "dev": true }, - "conventional-changelog": { - "version": "3.1.24", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.24.tgz", - "integrity": "sha512-ed6k8PO00UVvhExYohroVPXcOJ/K1N0/drJHx/faTH37OIZthlecuLIRX/T6uOp682CAoVoFpu+sSEaeuH6Asg==", - "dev": true, - "requires": { - "conventional-changelog-angular": "^5.0.12", - "conventional-changelog-atom": "^2.0.8", - "conventional-changelog-codemirror": "^2.0.8", - "conventional-changelog-conventionalcommits": "^4.5.0", - "conventional-changelog-core": "^4.2.1", - "conventional-changelog-ember": "^2.0.9", - "conventional-changelog-eslint": "^3.0.9", - "conventional-changelog-express": "^2.0.6", - "conventional-changelog-jquery": "^3.0.11", - "conventional-changelog-jshint": "^2.0.9", - "conventional-changelog-preset-loader": "^2.3.4" - } - }, - "conventional-changelog-angular": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", - "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", - "dev": true, - "requires": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - } - }, - "conventional-changelog-atom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", - "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", - "dev": true, - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-codemirror": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", - "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", - "dev": true, - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-config-spec": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz", - "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==", - "dev": true - }, - "conventional-changelog-conventionalcommits": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.1.tgz", - "integrity": "sha512-lzWJpPZhbM1R0PIzkwzGBCnAkH5RKJzJfFQZcl/D+2lsJxAwGnDKBqn/F4C1RD31GJNn8NuKWQzAZDAVXPp2Mw==", - "dev": true, - "requires": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" - } - }, - "conventional-changelog-core": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz", - "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==", - "dev": true, - "requires": { - "add-stream": "^1.0.0", - "conventional-changelog-writer": "^5.0.0", - "conventional-commits-parser": "^3.2.0", - "dateformat": "^3.0.0", - "get-pkg-repo": "^4.0.0", - "git-raw-commits": "^2.0.8", - "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^4.1.1", - "lodash": "^4.17.15", - "normalize-package-data": "^3.0.0", - "q": "^1.5.1", - "read-pkg": "^3.0.0", - "read-pkg-up": "^3.0.0", - "through2": "^4.0.0" - }, - "dependencies": { - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true - }, - "hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "requires": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "conventional-changelog-ember": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", - "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", - "dev": true, - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-eslint": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", - "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", - "dev": true, - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-express": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", - "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", - "dev": true, - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-jquery": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", - "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", - "dev": true, - "requires": { - "q": "^1.5.1" - } - }, - "conventional-changelog-jshint": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", - "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", - "dev": true, - "requires": { - "compare-func": "^2.0.0", - "q": "^1.5.1" - } - }, - "conventional-changelog-preset-loader": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", - "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", - "dev": true - }, - "conventional-changelog-writer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", - "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", - "dev": true, - "requires": { - "conventional-commits-filter": "^2.0.7", - "dateformat": "^3.0.0", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "semver": "^6.0.0", - "split": "^1.0.0", - "through2": "^4.0.0" - }, - "dependencies": { - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "requires": { - "through": "2" - } - } - } - }, - "conventional-commits-filter": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", - "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", - "dev": true, - "requires": { - "lodash.ismatch": "^4.4.0", - "modify-values": "^1.0.0" - } - }, - "conventional-commits-parser": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", - "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", - "dev": true, - "requires": { - "is-text-path": "^1.0.1", - "JSONStream": "^1.0.4", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "requires": { - "readable-stream": "^3.0.0" - } - } - } - }, - "conventional-recommended-bump": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", - "integrity": "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==", - "dev": true, - "requires": { - "concat-stream": "^2.0.0", - "conventional-changelog-preset-loader": "^2.3.4", - "conventional-commits-filter": "^2.0.7", - "conventional-commits-parser": "^3.2.0", - "git-raw-commits": "^2.0.8", - "git-semver-tags": "^4.1.1", - "meow": "^8.0.0", - "q": "^1.5.1" - }, - "dependencies": { - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "convert-source-map": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", @@ -28736,12 +27442,6 @@ "type": "^1.0.1" } }, - "dargs": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", - "dev": true - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -28766,7 +27466,7 @@ "de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true, "optional": true }, @@ -28806,22 +27506,13 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", "dev": true, "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - } + "character-entities": "^2.0.0" } }, "decode-uri-component": { @@ -28947,12 +27638,6 @@ "isobject": "^3.0.1" } }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -28964,6 +27649,12 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true + }, "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -28975,12 +27666,6 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, - "detect-indent": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", - "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", - "dev": true - }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -28993,17 +27678,6 @@ "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", "dev": true }, - "detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", - "dev": true, - "requires": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" - } - }, "devtools": { "version": "7.16.16", "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.16.tgz", @@ -29087,242 +27761,135 @@ } }, "documentation": { - "version": "13.2.5", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-13.2.5.tgz", - "integrity": "sha512-d1TrfrHXYZR63xrOzkYwwe297vkSwBoEhyyMBOi20T+7Ohe1aX1dW4nqXncQmdmE5MxluSaxxa3BW1dCvbF5AQ==", - "dev": true, - "requires": { - "@babel/core": "7.12.3", - "@babel/generator": "7.12.1", - "@babel/parser": "7.12.3", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "@vue/compiler-sfc": "^3.0.11", - "ansi-html": "^0.0.7", - "babelify": "^10.0.0", - "chalk": "^2.3.0", - "chokidar": "^3.4.0", - "concat-stream": "^1.6.0", - "diff": "^4.0.1", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.0.tgz", + "integrity": "sha512-4AwFzdiseEdtqR0KKLrruIQ5fvh7n5epg47P0ZyOidA5Fes5am+6xjqkDECHPwcv4pxJ6zITaHwzCoGblP0+JQ==", + "dev": true, + "requires": { + "@babel/core": "^7.18.10", + "@babel/generator": "^7.18.10", + "@babel/parser": "^7.18.11", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10", + "@vue/compiler-sfc": "^3.2.37", + "chalk": "^5.0.1", + "chokidar": "^3.5.3", + "diff": "^5.1.0", "doctrine-temporary-fork": "2.1.0", - "get-port": "^5.0.0", - "git-url-parse": "^11.1.2", - "github-slugger": "1.2.0", - "glob": "^7.1.2", - "globals-docs": "^2.4.0", - "highlight.js": "^10.7.2", - "ini": "^1.3.5", - "js-yaml": "^3.10.0", - "lodash": "^4.17.10", - "mdast-util-find-and-replace": "^1.1.1", + "git-url-parse": "^12.0.0", + "github-slugger": "1.4.0", + "glob": "^8.0.3", + "globals-docs": "^2.4.1", + "highlight.js": "^11.6.0", + "ini": "^3.0.0", + "js-yaml": "^4.1.0", + "konan": "^2.1.1", + "lodash": "^4.17.21", + "mdast-util-find-and-replace": "^2.2.1", "mdast-util-inject": "^1.1.0", - "micromatch": "^3.1.5", - "mime": "^2.2.0", - "module-deps-sortable": "^5.0.3", + "micromark-util-character": "^1.1.0", "parse-filepath": "^1.0.2", - "pify": "^5.0.0", - "read-pkg-up": "^4.0.0", - "remark": "^13.0.0", - "remark-gfm": "^1.0.0", - "remark-html": "^13.0.1", - "remark-reference-links": "^5.0.0", - "remark-toc": "^7.2.0", - "resolve": "^1.8.1", - "stream-array": "^1.1.2", - "strip-json-comments": "^2.0.1", - "tiny-lr": "^1.1.0", - "unist-builder": "^2.0.3", - "unist-util-visit": "^2.0.3", - "vfile": "^4.0.0", - "vfile-reporter": "^6.0.0", - "vfile-sort": "^2.1.0", - "vinyl": "^2.1.0", - "vinyl-fs": "^3.0.2", - "vue-template-compiler": "^2.6.12", - "yargs": "^15.3.1" - }, - "dependencies": { - "@babel/core": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", - "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.1", - "@babel/parser": "^7.12.3", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", - "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.1", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/parser": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", - "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==", + "pify": "^6.0.0", + "read-pkg-up": "^9.1.0", + "remark": "^14.0.2", + "remark-gfm": "^3.0.1", + "remark-html": "^15.0.1", + "remark-reference-links": "^6.0.1", + "remark-toc": "^8.0.1", + "resolve": "^1.22.1", + "strip-json-comments": "^5.0.0", + "unist-builder": "^3.0.0", + "unist-util-visit": "^4.1.0", + "vfile": "^5.3.4", + "vfile-reporter": "^7.0.4", + "vfile-sort": "^3.0.0", + "vue-template-compiler": "^2.7.8", + "yargs": "^17.5.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { + "brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "color-name": "~1.1.4" + "balanced-match": "^1.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", "dev": true }, "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", "dev": true }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } + "ini": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", + "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", + "dev": true }, - "p-locate": { + "js-yaml": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "argparse": "^2.0.1" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "brace-expansion": "^2.0.1" } }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "strip-json-comments": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", + "integrity": "sha512-V1LGY4UUo0jgwC+ELQ2BNWfPa17TIuwBLg+j1AA/9RPzKINl1lhxVEu2r+ZTTO8aetIsUzE5Qj6LMSBkoGYKKw==", "dev": true }, "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" } } } @@ -29339,74 +27906,10 @@ "void-elements": "^2.0.0" } }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "dotgitignore": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", - "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==", - "dev": true, - "requires": { - "find-up": "^3.0.0", - "minimatch": "^3.0.4" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - } - } - }, "dset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/dset/-/dset-2.0.1.tgz", - "integrity": "sha512-nI29OZMRYq36hOcifB6HTjajNAAiBKSXsyWZrq+VniusseuP2OpNlTiYgsaNRSGvpyq5Wjbc2gQLyBdTyWqhnQ==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", + "integrity": "sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==" }, "duplexer": { "version": "0.1.2", @@ -29469,6 +27972,12 @@ } } }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "easy-table": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", @@ -29505,23 +28014,23 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", - "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", + "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", "dev": true, "requires": { - "jake": "^10.6.1" + "jake": "^10.8.5" } }, "electron-to-chromium": { - "version": "1.4.78", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.78.tgz", - "integrity": "sha512-o61+D/Lx7j/E0LIin/efOqeHpXhwi1TaQco9vUcRmr91m25SfZY6L5hWJDv/r+6kNjboFKgBw1LbfM0lbhuK6Q==" + "version": "1.4.243", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.243.tgz", + "integrity": "sha512-BgLD2gBX43OSXwlT01oYRRD5NIB4n3okTRxkzEAC6G0SZG4TTlyrWMjbOo0fajCwqwpRtMHXQNMjtRN6qpNtfw==" }, "emoji-regex": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz", - "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, "emojis-list": { @@ -30610,6 +29119,17 @@ "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" + }, + "dependencies": { + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + } } }, "extglob": { @@ -30772,12 +29292,32 @@ "optional": true }, "filelist": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", - "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, "requires": { - "minimatch": "^3.0.4" + "minimatch": "^5.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "fill-range": { @@ -30789,12 +29329,6 @@ "to-regex-range": "^5.0.1" } }, - "filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", - "dev": true - }, "finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -30999,15 +29533,6 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", - "dev": true, - "requires": { - "null-check": "^1.0.0" - } - }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -31189,59 +29714,11 @@ "has-symbols": "^1.0.1" } }, - "get-pkg-repo": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", - "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", - "dev": true, - "requires": { - "@hutson/parse-repository-url": "^3.0.0", - "hosted-git-info": "^4.0.0", - "through2": "^2.0.0", - "yargs": "^16.2.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - } - } + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true }, "get-port": { "version": "5.1.1", @@ -31289,105 +29766,30 @@ "assert-plus": "^1.0.0" } }, - "git-raw-commits": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", - "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", - "dev": true, - "requires": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "requires": { - "readable-stream": "^3.0.0" - } - } - } - }, - "git-remote-origin-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", - "dev": true, - "requires": { - "gitconfiglocal": "^1.0.0", - "pify": "^2.3.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "git-semver-tags": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", - "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", - "dev": true, - "requires": { - "meow": "^8.0.0", - "semver": "^6.0.0" - } - }, "git-up": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.5.tgz", - "integrity": "sha512-YUvVDg/vX3d0syBsk/CKUTib0srcQME0JyHkL5BaYdwLsiCslPWmDSi8PUMo9pXYjrryMcmsCoCgsTpSCJEQaA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-6.0.0.tgz", + "integrity": "sha512-6RUFSNd1c/D0xtGnyWN2sxza2bZtZ/EmI9448n6rCZruFwV/ezeEn2fJP7XnUQGwf0RAtd/mmUCbtH6JPYA2SA==", "dev": true, "requires": { - "is-ssh": "^1.3.0", - "parse-url": "^6.0.0" + "is-ssh": "^1.4.0", + "parse-url": "^7.0.2" } }, "git-url-parse": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.6.0.tgz", - "integrity": "sha512-WWUxvJs5HsyHL6L08wOusa/IXYtMuCAhrMmnTjQPpBU0TTHyDhnOATNH3xNQz7YOQUsqIIPTGr4xiVti1Hsk5g==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-12.0.0.tgz", + "integrity": "sha512-I6LMWsxV87vysX1WfsoglXsXg6GjQRKq7+Dgiseo+h0skmp5Hp2rzmcEIRQot9CPA+uzU7x1x7jZdqvTFGnB+Q==", "dev": true, "requires": { - "git-up": "^4.0.0" - } - }, - "gitconfiglocal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", - "dev": true, - "requires": { - "ini": "^1.3.2" + "git-up": "^6.0.0" } }, "github-slugger": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.2.0.tgz", - "integrity": "sha512-wIaa75k1vZhyPm9yWrD08A5Xnx/V+RmzGrpjQuLemGKSb77Qukiaei58Bogrl/LZSADDfPzKJX8jhLs4CRTl7Q==", - "dev": true, - "requires": { - "emoji-regex": ">=6.0.0 <=6.1.1" - } + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", + "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==", + "dev": true }, "glob": { "version": "7.2.0", @@ -31415,7 +29817,7 @@ "glob-stream": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "integrity": "sha512-uMbLGAP3S2aDOHUDfdoYcdIePUCfysbAd0IAoWVZbeGU/oNQ8asHVSshLDJUPWxfzj8zsCG7/XeHPHTtow0nsw==", "dev": true, "requires": { "extend": "^3.0.0", @@ -31433,7 +29835,7 @@ "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", "dev": true, "requires": { "is-glob": "^3.1.0", @@ -31572,7 +29974,7 @@ "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", "dev": true, "requires": { "is-glob": "^3.1.0", @@ -31720,9 +30122,9 @@ } }, "got": { - "version": "11.8.3", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz", - "integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==", + "version": "11.8.5", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", + "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", "dev": true, "requires": { "@sindresorhus/is": "^4.0.0", @@ -31771,7 +30173,7 @@ "gulp-clean": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/gulp-clean/-/gulp-clean-0.3.2.tgz", - "integrity": "sha1-o0fUc6zqQBgvk1WHpFGUFnGSgQI=", + "integrity": "sha512-1pSOrC+PLKkEQz1HFl5L+TG0LV3/Tz/Y25lRk1IjTsE/agXWcqeeicBFJ1vDgokF+musc0vSBdm3xLXUU8cP1Q==", "dev": true, "requires": { "gulp-util": "^2.2.14", @@ -31849,7 +30251,7 @@ "gulp-util": { "version": "2.2.20", "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", - "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", + "integrity": "sha512-9rtv4sj9EtCWYGD15HQQvWtRBtU9g1t0+w29tphetHxjxEAuBKQJkhGqvlLkHEtUjEgoqIpsVwPKU1yMZAa+wA==", "dev": true, "requires": { "chalk": "^0.5.0", @@ -31936,7 +30338,7 @@ "lodash.template": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", - "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", + "integrity": "sha512-5yLOQwlS69xbaez3g9dA1i0GMAj8pLDHp8lhA4V7M1vRam1lqD76f0jg5EV+65frbqrXo1WH9ZfKalfYBzJ5yQ==", "dev": true, "requires": { "lodash._escapestringchar": "~2.4.1", @@ -31967,7 +30369,7 @@ "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "integrity": "sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==", "dev": true, "requires": { "camelcase-keys": "^2.0.0", @@ -31983,9 +30385,9 @@ }, "dependencies": { "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true } } @@ -32141,7 +30543,7 @@ "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "integrity": "sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==", "dev": true }, "vinyl": { @@ -32527,174 +30929,145 @@ } }, "gulp-eslint": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-4.0.2.tgz", - "integrity": "sha512-fcFUQzFsN6dJ6KZlG+qPOEkqfcevRUXgztkYCvhNvJeSvOicC8ucutN4qR/ID8LmNZx9YPIkBzazTNnVvbh8wg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", + "integrity": "sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig==", "dev": true, "requires": { - "eslint": "^4.0.0", + "eslint": "^6.0.0", "fancy-log": "^1.3.2", - "plugin-error": "^1.0.0" + "plugin-error": "^1.0.1" }, "dependencies": { - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true, - "requires": {} - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "color-name": "~1.1.4" } }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true }, "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", + "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", + "optionator": "^0.8.3", "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, - "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { @@ -32704,151 +31077,116 @@ "dev": true }, "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" } }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" + "flat-cache": "^2.0.1" } }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.5" + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" } }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" + "type-fest": "^0.8.1" } }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", "through": "^2.3.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" } }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "minimist": "^1.2.6" } }, "optionator": { @@ -32865,28 +31203,24 @@ "word-wrap": "~1.2.3" } }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true }, "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -32896,16 +31230,19 @@ "glob": "^7.1.3" } }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -32914,60 +31251,84 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true }, "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" } }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "has-flag": "^4.0.0" } }, "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } } }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, "requires": { "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -32976,46 +31337,6 @@ "requires": { "isexe": "^2.0.0" } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, - "gulp-footer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/gulp-footer/-/gulp-footer-2.1.0.tgz", - "integrity": "sha512-CK3nRBP3PG59XN2L1rDLkBHA7goYsW+tJuVQccLP9jq3mpBT2kuRq0ImgNjrUkDbF948aCVQH4J7uIEqiZ2MHA==", - "dev": true, - "requires": { - "lodash": "^4.17.21", - "map-stream": "^0.0.7" - } - }, - "gulp-header": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-2.0.9.tgz", - "integrity": "sha512-LMGiBx+qH8giwrOuuZXSGvswcIUh0OiioNkUpLhNyvaC6/Ga8X6cfAeme2L5PqsbXMhL8o8b/OmVqIQdxprhcQ==", - "dev": true, - "requires": { - "concat-with-sourcemaps": "^1.1.0", - "lodash.template": "^4.5.0", - "map-stream": "0.0.7", - "through2": "^2.0.0" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } } } }, @@ -33244,7 +31565,7 @@ "gulp-util": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "integrity": "sha512-q5oWPc12lwSFS9h/4VIjG+1NuNDlJ48ywV2JKItY4Ycc/n1fXJeYPVQsfu5ZrhQi7FGSDBalwUCLar/GyHXKGw==", "dev": true, "requires": { "array-differ": "^1.0.0", @@ -33327,7 +31648,7 @@ "lodash.template": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "integrity": "sha512-0B4Y53I0OgHUJkt+7RmlDFWKjVAI/YUpWNiL9GQz5ORDr4ttgfQGo+phBWKFLJbBdtOwgMuUkdOHOnPg45jKmQ==", "dev": true, "requires": { "lodash._basecopy": "^3.0.0", @@ -33464,12 +31785,6 @@ } } }, - "hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -33569,42 +31884,46 @@ } }, "hast-util-is-element": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz", - "integrity": "sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ==", - "dev": true + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.2.tgz", + "integrity": "sha512-thjnlGAnwP8ef/GSO1Q8BfVk2gundnc2peGQqEg2kUt/IqesiGg/5mSwN2fE7nLzy61pg88NG6xV+UrGOrx9EA==", + "dev": true, + "requires": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0" + } }, "hast-util-sanitize": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-3.0.2.tgz", - "integrity": "sha512-+2I0x2ZCAyiZOO/sb4yNLFmdwPBnyJ4PBkVTUMKMqBwYNA+lXSgOmoRXlJFazoyid9QPogRRKgKhVEodv181sA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.0.0.tgz", + "integrity": "sha512-pw56+69jq+QSr/coADNvWTmBPDy+XsmwaF5KnUys4/wM1jt/fZdl7GPxhXXXYdXnz3Gj3qMkbUCH2uKjvX0MgQ==", "dev": true, "requires": { - "xtend": "^4.0.0" + "@types/hast": "^2.0.0" } }, "hast-util-to-html": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-7.1.3.tgz", - "integrity": "sha512-yk2+1p3EJTEE9ZEUkgHsUSVhIpCsL/bvT8E5GzmWc+N1Po5gBw+0F8bo7dpxXR0nu0bQVxVZGX2lBGF21CmeDw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.3.tgz", + "integrity": "sha512-/D/E5ymdPYhHpPkuTHOUkSatxr4w1ZKrZsG0Zv/3C2SRVT0JFJG53VS45AMrBtYk0wp5A7ksEhiC8QaOZM95+A==", "dev": true, "requires": { - "ccount": "^1.0.0", - "comma-separated-tokens": "^1.0.0", - "hast-util-is-element": "^1.0.0", - "hast-util-whitespace": "^1.0.0", - "html-void-elements": "^1.0.0", - "property-information": "^5.0.0", - "space-separated-tokens": "^1.0.0", - "stringify-entities": "^3.0.1", - "unist-util-is": "^4.0.0", - "xtend": "^4.0.0" + "@types/hast": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-is-element": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.2", + "unist-util-is": "^5.0.0" } }, "hast-util-whitespace": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz", - "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz", + "integrity": "sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==", "dev": true }, "he": { @@ -33614,9 +31933,9 @@ "dev": true }, "highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", + "integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==", "dev": true }, "home-or-tmp": { @@ -33651,9 +31970,9 @@ "dev": true }, "html-void-elements": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", - "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", "dev": true }, "http-cache-semantics": { @@ -33766,12 +32085,6 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -33924,22 +32237,6 @@ } } }, - "is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "dev": true - }, - "is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dev": true, - "requires": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -33997,9 +32294,9 @@ "dev": true }, "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", "requires": { "has": "^1.0.3" } @@ -34030,12 +32327,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "dev": true - }, "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", @@ -34117,12 +32408,6 @@ "is-extglob": "^2.1.1" } }, - "is-hexadecimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", - "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", - "dev": true - }, "is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -34192,16 +32477,10 @@ "has-tostringtag": "^1.0.0" } }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true }, "is-plain-object": { @@ -34235,12 +32514,6 @@ "is-unc-path": "^1.0.0" } }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, "is-running": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", @@ -34260,12 +32533,12 @@ "dev": true }, "is-ssh": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.3.tgz", - "integrity": "sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", "dev": true, "requires": { - "protocols": "^1.1.0" + "protocols": "^2.0.1" } }, "is-stream": { @@ -34292,15 +32565,6 @@ "has-symbols": "^1.0.2" } }, - "is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", - "dev": true, - "requires": { - "text-extensions": "^1.0.0" - } - }, "is-typed-array": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.8.tgz", @@ -34499,18 +32763,6 @@ "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, "istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -34573,22 +32825,65 @@ } }, "jake": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", - "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "version": "10.8.5", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", + "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", "dev": true, "requires": { - "async": "0.9.x", - "chalk": "^2.4.2", + "async": "^3.2.3", + "chalk": "^4.0.2", "filelist": "^1.0.1", "minimatch": "^3.0.4" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -34872,12 +33167,9 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" }, "jsonfile": { "version": "6.1.0", @@ -34889,22 +33181,6 @@ "universalify": "^2.0.0" } }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, "jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -34981,15 +33257,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -35209,9 +33476,9 @@ }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true }, "strip-ansi": { @@ -35297,6 +33564,12 @@ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true }, + "kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true + }, "konan": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", @@ -35446,43 +33719,12 @@ "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", "dev": true }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", "dev": true }, - "loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", @@ -35675,12 +33917,6 @@ "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", "dev": true }, - "lodash.ismatch": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", - "dev": true - }, "lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", @@ -35832,9 +34068,9 @@ "dev": true }, "longest-streak": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", - "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.0.1.tgz", + "integrity": "sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==", "dev": true }, "loose-envify": { @@ -35931,12 +34167,6 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, - "map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", - "dev": true - }, "map-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", @@ -35953,13 +34183,10 @@ } }, "markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "dev": true, - "requires": { - "repeat-string": "^1.0.0" - } + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.2.tgz", + "integrity": "sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==", + "dev": true }, "marky": { "version": "1.2.4", @@ -36003,104 +34230,130 @@ } }, "mdast-util-definitions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", - "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz", + "integrity": "sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==", "dev": true, "requires": { - "unist-util-visit": "^2.0.0" + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" } }, "mdast-util-find-and-replace": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", - "integrity": "sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.1.tgz", + "integrity": "sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==", "dev": true, "requires": { - "escape-string-regexp": "^4.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" }, "dependencies": { "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true } } }, "mdast-util-from-markdown": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", - "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz", + "integrity": "sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==", "dev": true, "requires": { "@types/mdast": "^3.0.0", - "mdast-util-to-string": "^2.0.0", - "micromark": "~2.11.0", - "parse-entities": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" }, "dependencies": { "mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", + "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", "dev": true } } }, "mdast-util-gfm": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz", - "integrity": "sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.1.tgz", + "integrity": "sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==", "dev": true, "requires": { - "mdast-util-gfm-autolink-literal": "^0.1.0", - "mdast-util-gfm-strikethrough": "^0.2.0", - "mdast-util-gfm-table": "^0.1.0", - "mdast-util-gfm-task-list-item": "^0.1.0", - "mdast-util-to-markdown": "^0.6.1" + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" } }, "mdast-util-gfm-autolink-literal": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz", - "integrity": "sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.2.tgz", + "integrity": "sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==", + "dev": true, + "requires": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + } + }, + "mdast-util-gfm-footnote": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.1.tgz", + "integrity": "sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==", "dev": true, "requires": { - "ccount": "^1.0.0", - "mdast-util-find-and-replace": "^1.1.0", - "micromark": "^2.11.3" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" } }, "mdast-util-gfm-strikethrough": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz", - "integrity": "sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.1.tgz", + "integrity": "sha512-zKJbEPe+JP6EUv0mZ0tQUyLQOC+FADt0bARldONot/nefuISkaZFlmVK4tU6JgfyZGrky02m/I6PmehgAgZgqg==", "dev": true, "requires": { - "mdast-util-to-markdown": "^0.6.0" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" } }, "mdast-util-gfm-table": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz", - "integrity": "sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.4.tgz", + "integrity": "sha512-aEuoPwZyP4iIMkf2cLWXxx3EQ6Bmh2yKy9MVCg4i6Sd3cX80dcLEfXO/V4ul3pGH9czBK4kp+FAl+ZHmSUt9/w==", "dev": true, "requires": { - "markdown-table": "^2.0.0", - "mdast-util-to-markdown": "~0.6.0" + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" } }, "mdast-util-gfm-task-list-item": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz", - "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.1.tgz", + "integrity": "sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==", "dev": true, "requires": { - "mdast-util-to-markdown": "~0.6.0" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" } }, "mdast-util-inject": { @@ -36113,39 +34366,43 @@ } }, "mdast-util-to-hast": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.2.0.tgz", - "integrity": "sha512-JoPBfJ3gBnHZ18icCwHR50orC9kNH81tiR1gs01D8Q5YpV6adHNO9nKNuFBCJQ941/32PT1a63UF/DitmS3amQ==", + "version": "12.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.1.tgz", + "integrity": "sha512-dyindR2P7qOqXO1hQirZeGtVbiX7xlNQbw7gGaAwN4A1dh4+X8xU/JyYmRoyB8Fu1uPXzp7mlL5QwW7k+knvgA==", "dev": true, "requires": { + "@types/hast": "^2.0.0", "@types/mdast": "^3.0.0", - "@types/unist": "^2.0.0", - "mdast-util-definitions": "^4.0.0", + "@types/mdurl": "^1.0.0", + "mdast-util-definitions": "^5.0.0", "mdurl": "^1.0.0", - "unist-builder": "^2.0.0", - "unist-util-generated": "^1.0.0", - "unist-util-position": "^3.0.0", - "unist-util-visit": "^2.0.0" + "micromark-util-sanitize-uri": "^1.0.0", + "trim-lines": "^3.0.0", + "unist-builder": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" } }, "mdast-util-to-markdown": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz", - "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.3.0.tgz", + "integrity": "sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==", "dev": true, "requires": { + "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", - "longest-streak": "^2.0.0", - "mdast-util-to-string": "^2.0.0", - "parse-entities": "^2.0.0", - "repeat-string": "^1.0.0", - "zwitch": "^1.0.0" + "longest-streak": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" }, "dependencies": { "mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", + "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", "dev": true } } @@ -36157,38 +34414,54 @@ "dev": true }, "mdast-util-toc": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-5.1.0.tgz", - "integrity": "sha512-csimbRIVkiqc+PpFeKDGQ/Ck2N4f9FYH3zzBMMJzcxoKL8m+cM0n94xXm0I9eaxHnKdY9n145SGTdyJC7i273g==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.0.tgz", + "integrity": "sha512-0PuqZELXZl4ms1sF7Lqigrqik4Ll3UhbI+jdTrfw7pZ9QPawgl7LD4GQ8MkU7bT/EwiVqChNTbifa2jLLKo76A==", "dev": true, "requires": { - "@types/mdast": "^3.0.3", - "@types/unist": "^2.0.3", - "extend": "^3.0.2", - "github-slugger": "^1.2.1", - "mdast-util-to-string": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit": "^2.0.0" + "@types/extend": "^3.0.0", + "@types/github-slugger": "^1.0.0", + "@types/mdast": "^3.0.0", + "extend": "^3.0.0", + "github-slugger": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "unist-util-is": "^5.0.0", + "unist-util-visit": "^3.0.0" }, "dependencies": { - "github-slugger": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", - "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==", - "dev": true - }, "mdast-util-to-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", - "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz", + "integrity": "sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==", "dev": true + }, + "unist-util-visit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-3.1.0.tgz", + "integrity": "sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^4.0.0" + } + }, + "unist-util-visit-parents": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz", + "integrity": "sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + } } } }, "mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", "dev": true }, "media-typer": { @@ -36222,215 +34495,11 @@ "readable-stream": "^2.0.1" } }, - "meow": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", - "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", - "dev": true, - "requires": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "requires": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "dependencies": { - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - } - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - } - } - }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -36443,71 +34512,332 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromark": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz", - "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.0.10.tgz", + "integrity": "sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==", "dev": true, "requires": { + "@types/debug": "^4.0.0", "debug": "^4.0.0", - "parse-entities": "^2.0.0" + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "micromark-core-commonmark": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz", + "integrity": "sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==", + "dev": true, + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" } }, "micromark-extension-gfm": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz", - "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.1.tgz", + "integrity": "sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==", "dev": true, "requires": { - "micromark": "~2.11.0", - "micromark-extension-gfm-autolink-literal": "~0.5.0", - "micromark-extension-gfm-strikethrough": "~0.6.5", - "micromark-extension-gfm-table": "~0.4.0", - "micromark-extension-gfm-tagfilter": "~0.3.0", - "micromark-extension-gfm-task-list-item": "~0.3.0" + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" } }, "micromark-extension-gfm-autolink-literal": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz", - "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-extension-gfm-footnote": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.0.4.tgz", + "integrity": "sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==", "dev": true, "requires": { - "micromark": "~2.11.3" + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, "micromark-extension-gfm-strikethrough": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz", - "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.4.tgz", + "integrity": "sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==", "dev": true, "requires": { - "micromark": "~2.11.0" + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, "micromark-extension-gfm-table": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz", - "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.5.tgz", + "integrity": "sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==", "dev": true, "requires": { - "micromark": "~2.11.0" + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" } }, "micromark-extension-gfm-tagfilter": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz", - "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==", - "dev": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.1.tgz", + "integrity": "sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==", + "dev": true, + "requires": { + "micromark-util-types": "^1.0.0" + } }, "micromark-extension-gfm-task-list-item": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz", - "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.3.tgz", + "integrity": "sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==", + "dev": true, + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-destination": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz", + "integrity": "sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-label": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz", + "integrity": "sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-space": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz", + "integrity": "sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-title": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz", + "integrity": "sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==", + "dev": true, + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-whitespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz", + "integrity": "sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==", + "dev": true, + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz", + "integrity": "sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-chunked": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz", + "integrity": "sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-classify-character": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz", + "integrity": "sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==", + "dev": true, + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-combine-extensions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz", + "integrity": "sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==", + "dev": true, + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-decode-numeric-character-reference": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz", + "integrity": "sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-decode-string": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz", + "integrity": "sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==", + "dev": true, + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-encode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz", + "integrity": "sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==", + "dev": true + }, + "micromark-util-html-tag-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz", + "integrity": "sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==", + "dev": true + }, + "micromark-util-normalize-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz", + "integrity": "sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==", + "dev": true, + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-resolve-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz", + "integrity": "sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==", + "dev": true, + "requires": { + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-sanitize-uri": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz", + "integrity": "sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==", "dev": true, "requires": { - "micromark": "~2.11.0" + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" } }, + "micromark-util-subtokenize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz", + "integrity": "sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==", + "dev": true, + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-util-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz", + "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==", + "dev": true + }, + "micromark-util-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz", + "integrity": "sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -36636,12 +34966,6 @@ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -36654,26 +34978,8 @@ "minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } - } + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true }, "mixin-deep": { "version": "1.3.2", @@ -36763,13 +35069,13 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", "dev": true, "requires": { "minimist": "0.0.8" @@ -36792,93 +35098,6 @@ } } }, - "modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", - "dev": true - }, - "module-deps-sortable": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/module-deps-sortable/-/module-deps-sortable-5.0.3.tgz", - "integrity": "sha512-eiyIZj/A0dj1o4ywXWqicazUL3l0HP3TydUR6xF0X3xh3LGBMLqW8a9aFe6MuNH4mxNMk53QKBHM6LOPR8kSgw==", - "dev": true, - "requires": { - "browser-resolve": "^1.7.0", - "cached-path-relative": "^1.0.0", - "concat-stream": "~1.5.0", - "defined": "^1.0.0", - "detective": "^5.2.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "JSONStream": "^1.0.3", - "konan": "^2.1.1", - "readable-stream": "^2.0.2", - "resolve": "^1.1.3", - "standard-version": "^9.0.0", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "~2.0.0", - "typedarray": "~0.0.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - } - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, "morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -36915,6 +35134,12 @@ } } }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true + }, "mrmime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.0.tgz", @@ -36990,9 +35215,9 @@ "optional": true }, "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true, "optional": true }, @@ -37117,9 +35342,9 @@ } }, "node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, "nopt": { "version": "3.0.6", @@ -37188,12 +35413,6 @@ } } }, - "null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", - "dev": true - }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -37601,20 +35820,6 @@ "callsites": "^3.0.0" } }, - "parse-entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", - "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", - "dev": true, - "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, "parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -37627,13 +35832,15 @@ } }, "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "requires": { + "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" } }, "parse-ms": { @@ -37655,27 +35862,24 @@ "dev": true }, "parse-path": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.3.tgz", - "integrity": "sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-5.0.0.tgz", + "integrity": "sha512-qOpH55/+ZJ4jUu/oLO+ifUKjFPNZGfnPJtzvGzKN/4oLMil5m9OH4VpOj6++9/ytJcfks4kzH2hhi87GL/OU9A==", "dev": true, "requires": { - "is-ssh": "^1.3.0", - "protocols": "^1.4.0", - "qs": "^6.9.4", - "query-string": "^6.13.8" + "protocols": "^2.0.0" } }, "parse-url": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-6.0.0.tgz", - "integrity": "sha512-cYyojeX7yIIwuJzledIHeLUBVJ6COVLeT4eF+2P6aKVzwvgKQPndCBv3+yQ7pcWjqToYwaligxzSYNNmGoMAvw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", + "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", "dev": true, "requires": { - "is-ssh": "^1.3.0", + "is-ssh": "^1.4.0", "normalize-url": "^6.1.0", - "parse-path": "^4.0.0", - "protocols": "^1.4.0" + "parse-path": "^5.0.0", + "protocols": "^2.0.1" } }, "parseurl": { @@ -37707,12 +35911,6 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -37744,23 +35942,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -37800,9 +35981,9 @@ "dev": true }, "pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-6.1.0.tgz", + "integrity": "sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==", "dev": true }, "pinkie": { @@ -37903,12 +36084,6 @@ } } }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -37916,13 +36091,13 @@ "dev": true }, "postcss": { - "version": "8.4.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.8.tgz", - "integrity": "sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==", + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", "dev": true, "optional": true, "requires": { - "nanoid": "^3.3.1", + "nanoid": "^3.3.4", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -37992,18 +36167,15 @@ "dev": true }, "property-information": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", - "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", - "dev": true, - "requires": { - "xtend": "^4.0.0" - } + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz", + "integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==", + "dev": true }, "protocols": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", - "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", "dev": true }, "proxy-addr": { @@ -38176,18 +36348,6 @@ "integrity": "sha512-bK0/0cCI+R8ZmOF1QjT7HupDUYCxbf/9TJgAmSXQxZpftXmTAeil9DRoCnTDkWbvOyZzhcMBwKpptWcdkGFIMg==", "dev": true }, - "query-string": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", - "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", - "dev": true, - "requires": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } - }, "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", @@ -38238,67 +36398,119 @@ "dev": true }, "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", + "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", "dev": true, "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^2.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + } } }, "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", + "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", "dev": true, "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" + "find-up": "^6.3.0", + "read-pkg": "^7.1.0", + "type-fest": "^2.5.0" }, "dependencies": { "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" } }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.1.1.tgz", + "integrity": "sha512-vJXaRMJgRVD3+cUZs3Mncj2mxpt5mP0EmNOsxRSZRMlbqjvxzDEOIUWXGmavo0ZC9+tNZCBLQ66reA11nbpHZg==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^6.0.0" } }, "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^1.0.0" } }, "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "^4.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + }, + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", "dev": true } } @@ -38373,16 +36585,6 @@ } } }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -38399,7 +36601,8 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true }, "regenerator-transform": { "version": "0.14.5", @@ -38469,72 +36672,84 @@ } }, "remark": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-13.0.0.tgz", - "integrity": "sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.2.tgz", + "integrity": "sha512-A3ARm2V4BgiRXaUo5K0dRvJ1lbogrbXnhkJRmD0yw092/Yl0kOCZt1k9ZeElEwkZsWGsMumz6qL5MfNJH9nOBA==", "dev": true, "requires": { - "remark-parse": "^9.0.0", - "remark-stringify": "^9.0.0", - "unified": "^9.1.0" + "@types/mdast": "^3.0.0", + "remark-parse": "^10.0.0", + "remark-stringify": "^10.0.0", + "unified": "^10.0.0" } }, "remark-gfm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz", - "integrity": "sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", "dev": true, "requires": { - "mdast-util-gfm": "^0.1.0", - "micromark-extension-gfm": "^0.3.0" + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" } }, "remark-html": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-13.0.2.tgz", - "integrity": "sha512-LhSRQ+3RKdBqB/RGesFWkNNfkGqprDUCwjq54SylfFeNyZby5kqOG8Dn/vYsRoM8htab6EWxFXCY6XIZvMoRiQ==", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.1.tgz", + "integrity": "sha512-7ta5UPRqj8nP0GhGMYUAghZ/DRno7dgq7alcW90A7+9pgJsXzGJlFgwF8HOP1b1tMgT3WwbeANN+CaTimMfyNQ==", "dev": true, "requires": { - "hast-util-sanitize": "^3.0.0", - "hast-util-to-html": "^7.0.0", - "mdast-util-to-hast": "^10.0.0" + "@types/mdast": "^3.0.0", + "hast-util-sanitize": "^4.0.0", + "hast-util-to-html": "^8.0.0", + "mdast-util-to-hast": "^12.0.0", + "unified": "^10.0.0" } }, "remark-parse": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz", - "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz", + "integrity": "sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==", "dev": true, "requires": { - "mdast-util-from-markdown": "^0.8.0" + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" } }, "remark-reference-links": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-5.0.0.tgz", - "integrity": "sha512-oSIo6lfDyG/1yYl2jPZNXmD9dgyPxp07mSd7snJagVMsDU6NRlD8i54MwHWUgMoOHTs8lIKPkwaUok/tbr5syQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-6.0.1.tgz", + "integrity": "sha512-34wY2C6HXSuKVTRtyJJwefkUD8zBOZOSHFZ4aSTnU2F656gr9WeuQ2dL6IJDK3NPd2F6xKF2t4XXcQY9MygAXg==", "dev": true, "requires": { - "unist-util-visit": "^2.0.0" + "@types/mdast": "^3.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" } }, "remark-stringify": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-9.0.1.tgz", - "integrity": "sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.2.tgz", + "integrity": "sha512-6wV3pvbPvHkbNnWB0wdDvVFHOe1hBRAx1Q/5g/EpH4RppAII6J8Gnwe7VbHuXaoKIF6LAg6ExTel/+kNqSQ7lw==", "dev": true, "requires": { - "mdast-util-to-markdown": "^0.6.0" + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.0.0", + "unified": "^10.0.0" } }, "remark-toc": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-7.2.0.tgz", - "integrity": "sha512-ppHepvpbg7j5kPFmU5rzDC4k2GTcPDvWcxXyr/7BZzO1cBSPk0stKtEJdsgAyw2WHKPGxadcHIZRjb2/sHxjkg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-8.0.1.tgz", + "integrity": "sha512-7he2VOm/cy13zilnOTZcyAoyoolV26ULlon6XyCFU+vG54Z/LWJnwphj/xKIDLOt66QmJUgTyUvLVHi2aAElyg==", "dev": true, "requires": { - "@types/unist": "^2.0.3", - "mdast-util-toc": "^5.0.0" + "@types/mdast": "^3.0.0", + "mdast-util-toc": "^6.0.0", + "unified": "^10.0.0" } }, "remove-bom-buffer": { @@ -38681,30 +36896,6 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - } - } - }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -38712,11 +36903,11 @@ "dev": true }, "resolve": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", - "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", "requires": { - "is-core-module": "^2.8.1", + "is-core-module": "^2.9.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -38827,21 +37018,6 @@ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, - "requires": { - "rx-lite": "*" - } - }, "rxjs": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", @@ -38859,6 +37035,15 @@ } } }, + "sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "requires": { + "mri": "^1.1.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -39461,10 +37646,17 @@ "debug": "~4.3.1" } }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true }, "source-map-js": { "version": "1.0.2", @@ -39515,9 +37707,9 @@ "optional": true }, "space-separated-tokens": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", - "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", + "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==", "dev": true }, "sparkles": { @@ -39567,12 +37759,6 @@ "through": "2" } }, - "split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "dev": true - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -39634,110 +37820,6 @@ } } }, - "standard-version": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.3.2.tgz", - "integrity": "sha512-u1rfKP4o4ew7Yjbfycv80aNMN2feTiqseAhUhrrx2XtdQGmu7gucpziXe68Z4YfHVqlxVEzo4aUA0Iu3VQOTgQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "conventional-changelog": "3.1.24", - "conventional-changelog-config-spec": "2.1.0", - "conventional-changelog-conventionalcommits": "4.6.1", - "conventional-recommended-bump": "6.1.0", - "detect-indent": "^6.0.0", - "detect-newline": "^3.1.0", - "dotgitignore": "^2.1.0", - "figures": "^3.1.0", - "find-up": "^5.0.0", - "fs-access": "^1.0.1", - "git-semver-tags": "^4.0.0", - "semver": "^7.1.1", - "stringify-package": "^1.0.1", - "yargs": "^16.0.0" - }, - "dependencies": { - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - } - } - }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -39821,50 +37903,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, - "stream-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/stream-array/-/stream-array-1.1.2.tgz", - "integrity": "sha1-nl9zRfITfDDuO0mLkRToC1K7frU=", - "dev": true, - "requires": { - "readable-stream": "~2.1.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "readable-stream": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", - "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=", - "dev": true, - "requires": { - "buffer-shims": "^1.0.0", - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, "stream-buffers": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", @@ -39880,16 +37918,6 @@ "duplexer": "~0.1.1" } }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "dev": true, - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -39913,12 +37941,6 @@ "fs-extra": "^10.0.1" } }, - "strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", - "dev": true - }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -39974,22 +37996,15 @@ } }, "stringify-entities": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.1.0.tgz", - "integrity": "sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", "dev": true, "requires": { - "character-entities-html4": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "xtend": "^4.0.0" + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" } }, - "stringify-package": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", - "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", - "dev": true - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -40017,30 +38032,6 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "dev": true, - "requires": { - "minimist": "^1.1.0" - } - }, "suffix": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/suffix/-/suffix-0.1.1.tgz", @@ -40193,14 +38184,14 @@ } }, "terser": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.12.0.tgz", - "integrity": "sha512-R3AUhNBGWiFc77HXag+1fXpAxTAFRQTJemlJKjAgD9r8xXTpjNKqIXwHM/o7Rh+O0kUJtS3WQVdBeMKFk5sw9A==", + "version": "5.14.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", + "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "dev": true, "requires": { + "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", "commander": "^2.20.0", - "source-map": "~0.7.2", "source-map-support": "~0.5.20" }, "dependencies": { @@ -40215,12 +38206,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true } } }, @@ -40275,11 +38260,16 @@ } } }, - "text-extensions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", - "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", - "dev": true + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } }, "text-table": { "version": "0.2.0", @@ -40390,12 +38380,12 @@ } }, "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "rimraf": "^3.0.0" } }, "to-absolute-glob": { @@ -40522,10 +38512,10 @@ "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", "dev": true }, - "trim-newlines": { + "trim-lines": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "dev": true }, "trim-right": { @@ -40535,9 +38525,9 @@ "dev": true }, "trough": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", - "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", "dev": true }, "tsconfig-paths": { @@ -40627,9 +38617,9 @@ "dev": true }, "typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz", + "integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==", "dev": true, "peer": true }, @@ -40752,25 +38742,18 @@ "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==" }, "unified": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", - "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", "dev": true, "requires": { - "bail": "^1.0.0", + "@types/unist": "^2.0.0", + "bail": "^2.0.0", "extend": "^3.0.0", "is-buffer": "^2.0.0", - "is-plain-obj": "^2.0.0", - "trough": "^1.0.0", - "vfile": "^4.0.0" - }, - "dependencies": { - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - } + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" } }, "union-value": { @@ -40804,57 +38787,63 @@ } }, "unist-builder": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", - "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz", + "integrity": "sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0" + } }, "unist-util-generated": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", - "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz", + "integrity": "sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==", "dev": true }, "unist-util-is": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", - "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz", + "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==", "dev": true }, "unist-util-position": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", - "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==", - "dev": true + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz", + "integrity": "sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0" + } }, "unist-util-stringify-position": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", - "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz", + "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==", "dev": true, "requires": { - "@types/unist": "^2.0.2" + "@types/unist": "^2.0.0" } }, "unist-util-visit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", - "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz", + "integrity": "sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==", "dev": true, "requires": { "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0", - "unist-util-visit-parents": "^3.0.0" + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" } }, "unist-util-visit-parents": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", - "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz", + "integrity": "sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==", "dev": true, "requires": { "@types/unist": "^2.0.0", - "unist-util-is": "^4.0.0" + "unist-util-is": "^5.0.0" } }, "universalify": { @@ -40937,6 +38926,15 @@ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", "dev": true }, + "update-browserslist-db": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz", + "integrity": "sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -41017,6 +39015,18 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, + "uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "requires": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + } + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -41073,69 +39083,92 @@ } }, "vfile": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", - "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.4.tgz", + "integrity": "sha512-KI+7cnst03KbEyN1+JE504zF5bJBZa+J+CrevLeyIMq0aPU681I2rQ5p4PlnQ6exFtWiUrg26QUdFMnAKR6PIw==", "dev": true, "requires": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-message": "^2.0.0" + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" } }, "vfile-message": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", - "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz", + "integrity": "sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==", "dev": true, "requires": { "@types/unist": "^2.0.0", - "unist-util-stringify-position": "^2.0.0" + "unist-util-stringify-position": "^3.0.0" } }, "vfile-reporter": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-6.0.2.tgz", - "integrity": "sha512-GN2bH2gs4eLnw/4jPSgfBjo+XCuvnX9elHICJZjVD4+NM0nsUrMTvdjGY5Sc/XG69XVTgLwj7hknQVc6M9FukA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.4.tgz", + "integrity": "sha512-4cWalUnLrEnbeUQ+hARG5YZtaHieVK3Jp4iG5HslttkVl+MHunSGNAIrODOTLbtjWsNZJRMCkL66AhvZAYuJ9A==", "dev": true, "requires": { - "repeat-string": "^1.5.0", - "string-width": "^4.0.0", - "supports-color": "^6.0.0", - "unist-util-stringify-position": "^2.0.0", - "vfile-sort": "^2.1.2", - "vfile-statistics": "^1.1.0" + "@types/supports-color": "^8.0.0", + "string-width": "^5.0.0", + "supports-color": "^9.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-sort": "^3.0.0", + "vfile-statistics": "^2.0.0" }, "dependencies": { - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" } + }, + "supports-color": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.3.tgz", + "integrity": "sha512-aszYUX/DVK/ed5rFLb/dDinVJrQjG/vmU433wtqVSD800rYsJNWxh2R3USV90aLSU+UsyQkbNeffVLzc6B6foA==", + "dev": true } } }, "vfile-sort": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-2.2.2.tgz", - "integrity": "sha512-tAyUqD2R1l/7Rn7ixdGkhXLD3zsg+XLAeUDUhXearjfIcpL1Hcsj5hHpCoy/gvfK/Ws61+e972fm0F7up7hfYA==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.0.tgz", + "integrity": "sha512-fJNctnuMi3l4ikTVcKpxTbzHeCgvDhnI44amA3NVDvA6rTC6oKCFpCVyT5n2fFMr3ebfr+WVQZedOCd73rzSxg==", + "dev": true, + "requires": { + "vfile-message": "^3.0.0" + } }, "vfile-statistics": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-1.1.4.tgz", - "integrity": "sha512-lXhElVO0Rq3frgPvFBwahmed3X03vjPF8OcjKMy8+F1xU/3Q3QU3tKEDp743SFtb74PdF0UWpxPvtOP0GCLheA==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.0.tgz", + "integrity": "sha512-foOWtcnJhKN9M2+20AOTlWi2dxNfAoeNIoxD5GXcO182UJyId4QrXa41fWrgcfV3FWTjdEDy3I4cpLVcQscIMA==", + "dev": true, + "requires": { + "vfile-message": "^3.0.0" + } }, "vinyl": { "version": "2.2.1", @@ -41238,14 +39271,23 @@ "dev": true }, "vue-template-compiler": { - "version": "2.6.14", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", - "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz", + "integrity": "sha512-QO+8R9YRq1Gudm8ZMdo/lImZLJVUIAM8c07Vp84ojdDAf8HmPJc7XB556PcXV218k2AkKznsRz6xB5uOjAC4EQ==", "dev": true, "optional": true, "requires": { "de-indent": "^1.0.2", - "he": "^1.1.0" + "he": "^1.2.0" + }, + "dependencies": { + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "optional": true + } } }, "walk": { @@ -41519,6 +39561,34 @@ } } }, + "webpack-manifest-plugin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.0.tgz", + "integrity": "sha512-8RQfMAdc5Uw3QbCQ/CBV/AXqOR8mt03B6GJmRbhWopE8GzRfEpn+k0ZuWywxW+5QZsffhmFDY1J6ohqJo+eMuw==", + "dev": true, + "requires": { + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "webpack-sources": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", + "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "dev": true, + "requires": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + } + } + } + }, "webpack-merge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", @@ -41611,12 +39681,6 @@ "is-weakset": "^2.0.1" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, "which-typed-array": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.7.tgz", @@ -41693,21 +39757,21 @@ "dev": true }, "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, "requires": { "mkdirp": "^0.5.1" }, "dependencies": { "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } } } @@ -41910,9 +39974,9 @@ } }, "zwitch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", - "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz", + "integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==", "dev": true } } diff --git a/package.json b/package.json index 2bf35a2a0dd..f91ad0bab9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.29.0-pre", + "version": "7.16.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -35,7 +35,6 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", - "@jsdevtools/coverage-istanbul-loader": "^3.0.3", "@wdio/browserstack-service": "^7.16.0", "@wdio/cli": "^7.5.2", "@wdio/concise-reporter": "^7.5.2", @@ -46,12 +45,13 @@ "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", + "babel-plugin-istanbul": "^6.1.1", "babel-register": "^6.26.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", "deep-equal": "^2.0.3", - "documentation": "^13.2.5", + "documentation": "^14.0.0", "es5-shim": "^4.5.14", "eslint": "^7.27.0", "eslint-config-standard": "^10.2.1", @@ -67,9 +67,7 @@ "gulp-clean": "^0.3.2", "gulp-concat": "^2.6.0", "gulp-connect": "^5.7.0", - "gulp-eslint": "^4.0.0", - "gulp-footer": "^2.0.2", - "gulp-header": "^2.0.9", + "gulp-eslint": "^6.0.0", "gulp-if": "^3.0.0", "gulp-js-escape": "^1.0.1", "gulp-replace": "^1.0.0", @@ -110,19 +108,21 @@ "webdriverio": "^7.6.1", "webpack": "^5.70.0", "webpack-bundle-analyzer": "^4.5.0", + "webpack-manifest-plugin": "^5.0.0", "webpack-stream": "^7.0.0", "yargs": "^1.3.1" }, "dependencies": { "@babel/core": "^7.16.7", + "@babel/plugin-transform-runtime": "^7.18.9", "@babel/preset-env": "^7.16.8", - "babel-plugin-transform-object-assign": "^6.22.0", + "@babel/runtime": "^7.18.9", "core-js": "^3.13.0", "core-js-pure": "^3.13.0", "criteo-direct-rsa-validate": "^1.1.0", "crypto-js": "^3.3.0", "dlv": "1.1.3", - "dset": "2.0.1", + "dset": "3.1.2", "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", diff --git a/plugins/pbjsGlobals.js b/plugins/pbjsGlobals.js index 79dafd1e8b2..c1a08133e8e 100644 --- a/plugins/pbjsGlobals.js +++ b/plugins/pbjsGlobals.js @@ -2,13 +2,38 @@ let t = require('@babel/core').types; let prebid = require('../package.json'); const path = require('path'); +const allFeatures = new Set(require('../features.json')); + +const FEATURES_GLOBAL = 'FEATURES'; + +function featureMap(disable = []) { + disable = disable.map((s) => s.toUpperCase()); + disable.forEach((f) => { + if (!allFeatures.has(f)) { + throw new Error(`Unrecognized feature: ${f}`) + } + }); + disable = new Set(disable); + return Object.fromEntries([...allFeatures.keys()].map((f) => [f, !disable.has(f)])); +} + +function getNpmVersion(version) { + try { + // only use "real" versions (that is, not the -pre ones, they won't be on jsDelivr) + return /^([\d.]+)$/.exec(version)[1]; + } catch (e) { + return 'latest'; + } +} module.exports = function(api, options) { const pbGlobal = options.globalVarName || prebid.globalVarName; + const features = featureMap(options.disableFeatures); let replace = { '$prebid.version$': prebid.version, '$$PREBID_GLOBAL$$': pbGlobal, - '$$REPO_AND_VERSION$$': `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}` + '$$REPO_AND_VERSION$$': `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}`, + '$$PREBID_DIST_URL_BASE$$': options.prebidDistUrlBase || `https://cdn.jsdelivr.net/npm/prebid.js@${getNpmVersion(prebid.version)}/dist/` }; let identifierToStringLiteral = [ @@ -82,6 +107,17 @@ module.exports = function(api, options) { } } }); + }, + MemberExpression(path) { + if ( + t.isIdentifier(path.node.object) && + path.node.object.name === FEATURES_GLOBAL && + !path.scope.hasBinding(FEATURES_GLOBAL) && + t.isIdentifier(path.node.property) && + features.hasOwnProperty(path.node.property.name) + ) { + path.replaceWith(t.booleanLiteral(features[path.node.property.name])); + } } } }; diff --git a/src/Renderer.js b/src/Renderer.js index f26a5a377c0..97e37084e89 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -14,7 +14,7 @@ const moduleCode = 'outstream'; */ export function Renderer(options) { - const { url, config, id, callback, loaded, adUnitCode } = options; + const { url, config, id, callback, loaded, adUnitCode, renderNow } = options; this.url = url; this.config = config; this.handlers = {}; @@ -50,19 +50,21 @@ export function Renderer(options) { } } - if (!isRendererPreferredFromAdUnit(adUnitCode)) { + if (isRendererPreferredFromAdUnit(adUnitCode)) { + logWarn(`External Js not loaded by Renderer since renderer url and callback is already defined on adUnit ${adUnitCode}`); + runRender(); + } else if (renderNow) { + runRender(); + } else { // we expect to load a renderer url once only so cache the request to load script this.cmd.unshift(runRender) // should render run first ? loadExternalScript(url, moduleCode, this.callback, this.documentContext); - } else { - logWarn(`External Js not loaded by Renderer since renderer url and callback is already defined on adUnit ${adUnitCode}`); - runRender() } - }.bind(this) // bind the function to this object to avoid 'this' errors + }.bind(this); // bind the function to this object to avoid 'this' errors } -Renderer.install = function({ url, config, id, callback, loaded, adUnitCode }) { - return new Renderer({ url, config, id, callback, loaded, adUnitCode }); +Renderer.install = function({ url, config, id, callback, loaded, adUnitCode, renderNow }) { + return new Renderer({ url, config, id, callback, loaded, adUnitCode, renderNow }); }; Renderer.prototype.getConfig = function() { diff --git a/src/adapterManager.js b/src/adapterManager.js index 8ddffcd24e7..ffad35e65a2 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -17,7 +17,7 @@ import { logError, logInfo, logMessage, - logWarn, + logWarn, mergeDeep, shuffle, timestamp, } from './utils.js'; @@ -66,10 +66,10 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src}) { .reduce((bids, bid) => { bid = Object.assign({}, bid, getDefinedParams(adUnit, [ 'nativeParams', + 'nativeOrtbRequest', 'ortb2Imp', 'mediaType', - 'renderer', - 'storedAuctionResponse' + 'renderer' ])); const mediaTypes = bid.mediaTypes == null ? adUnit.mediaTypes : bid.mediaTypes @@ -206,13 +206,15 @@ export function _partitionBidders (adUnits, s2sConfigs, {getS2SBidders = getS2SB export const partitionBidders = hook('sync', _partitionBidders, 'partitionBidders'); -adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, auctionId, cbTimeout, labels) { +adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, auctionId, cbTimeout, labels, ortb2Fragments = {}) { /** * emit and pass adunits for external modification * @see {@link https://github.com/prebid/Prebid.js/issues/4149|Issue} */ events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, adUnits); - decorateAdUnitsWithNativeParams(adUnits); + if (FEATURES.NATIVE) { + decorateAdUnitsWithNativeParams(adUnits); + } adUnits = setupAdUnitMediaTypes(adUnits, labels); let {[PARTITIONS.CLIENT]: clientBidders, [PARTITIONS.SERVER]: serverBidders} = partitionBidders(adUnits, _s2sConfigs); @@ -224,6 +226,16 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a let bidRequests = []; + const ortb2 = ortb2Fragments.global || {}; + const bidderOrtb2 = ortb2Fragments.bidder || {}; + + function addOrtb2(bidderRequest) { + const fpd = Object.freeze(mergeDeep({}, ortb2, bidderOrtb2[bidderRequest.bidderCode])); + bidderRequest.ortb2 = fpd; + bidderRequest.bids.forEach((bid) => bid.ortb2 = fpd); + return bidderRequest; + } + _s2sConfigs.forEach(s2sConfig => { if (s2sConfig && s2sConfig.enabled) { let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits, s2sConfig); @@ -232,7 +244,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a let uniquePbsTid = generateUUID(); serverBidders.forEach(bidderCode => { const bidderRequestId = getUniqueIdentifierStr(); - const bidderRequest = { + const bidderRequest = addOrtb2({ bidderCode, auctionId, bidderRequestId, @@ -241,8 +253,8 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a auctionStart: auctionStart, timeout: s2sConfig.timeout, src: CONSTANTS.S2S.SRC, - refererInfo - }; + refererInfo, + }); if (bidderRequest.bids.length !== 0) { bidRequests.push(bidderRequest); } @@ -263,21 +275,21 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a } }); } - }) + }); // client adapters let adUnitsClientCopy = getAdUnitCopyForClientAdapters(adUnits); clientBidders.forEach(bidderCode => { const bidderRequestId = getUniqueIdentifierStr(); - const bidderRequest = { + const bidderRequest = addOrtb2({ bidderCode, auctionId, bidderRequestId, bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': deepClone(adUnitsClientCopy), labels, src: 'client'}), auctionStart: auctionStart, timeout: cbTimeout, - refererInfo - }; + refererInfo, + }); const adapter = _bidderRegistry[bidderCode]; if (!adapter) { logError(`Trying to make a request for bidder that does not exist: ${bidderCode}`); @@ -299,10 +311,18 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a bidRequest['uspConsent'] = uspDataHandler.getConsentData(); }); } + + bidRequests.forEach(bidRequest => { + config.runWithBidder(bidRequest.bidderCode, () => { + const fledgeEnabledFromConfig = config.getConfig('fledgeEnabled'); + bidRequest['fledgeEnabled'] = navigator.runAdAuction && fledgeEnabledFromConfig + }); + }); + return bidRequests; }, 'makeBidRequests'); -adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, requestCallbacks, requestBidsTimeout, onTimelyResponse) => { +adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, requestCallbacks, requestBidsTimeout, onTimelyResponse, ortb2Fragments = {}) => { if (!bidRequests.length) { logWarn('callBids executed with no bidRequests. Were they filtered by labels or sizing?'); return; @@ -346,7 +366,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request let uniqueServerRequests = serverBidRequests.filter(serverBidRequest => serverBidRequest.uniquePbsTid === uniquePbsTid); if (s2sAdapter) { - let s2sBidRequest = {tid: sourceTid, 'ad_units': adUnitsS2SCopy, s2sConfig}; + let s2sBidRequest = {tid: sourceTid, 'ad_units': adUnitsS2SCopy, s2sConfig, ortb2Fragments}; if (s2sBidRequest.ad_units.length) { let doneCbs = uniqueServerRequests.map(bidRequest => { bidRequest.start = timestamp(); @@ -374,7 +394,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request } else { logError('missing ' + s2sConfig.adapter); } - counter++ + counter++; } }); @@ -416,7 +436,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request function getSupportedMediaTypes(bidderCode) { let supportedMediaTypes = []; if (includes(adapterManager.videoAdapters, bidderCode)) supportedMediaTypes.push('video'); - if (includes(nativeAdapters, bidderCode)) supportedMediaTypes.push('native'); + if (FEATURES.NATIVE && includes(nativeAdapters, bidderCode)) supportedMediaTypes.push('native'); return supportedMediaTypes; } @@ -430,7 +450,7 @@ adapterManager.registerBidAdapter = function (bidAdapter, bidderCode, {supported if (includes(supportedMediaTypes, 'video')) { adapterManager.videoAdapters.push(bidderCode); } - if (includes(supportedMediaTypes, 'native')) { + if (FEATURES.NATIVE && includes(supportedMediaTypes, 'native')) { nativeAdapters.push(bidderCode); } } else { @@ -461,7 +481,7 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias, options) { }); nonS2SAlias.forEach(bidderCode => { logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adapterManager.aliasBidAdapter'); - }) + }); } else { try { let newAdapter; diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 382c93b0d25..2e28388d188 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -9,7 +9,7 @@ import CONSTANTS from '../constants.json'; import * as events from '../events.js'; import {includes} from '../polyfill.js'; import { ajax } from '../ajax.js'; -import { logWarn, logError, parseQueryStringParameters, delayExecution, parseSizesInput, flatten, uniques, timestamp, deepAccess, isArray, isPlainObject } from '../utils.js'; +import { logWarn, logInfo, logError, parseQueryStringParameters, delayExecution, parseSizesInput, flatten, uniques, timestamp, deepAccess, isArray, isPlainObject } from '../utils.js'; import { ADPOD } from '../mediaTypes.js'; import { getHook, hook } from '../hook.js'; import { getCoreStorageManager } from '../storageManager.js'; @@ -72,6 +72,13 @@ export const storage = getCoreStorageManager('bidderFactory'); * @property {object} params Any bidder-specific params which the publisher used in their bid request. */ +/** + * @typedef {object} BidderAuctionResponse An object encapsulating an adapter response for current Auction + * + * @property {Array} bids Contextual bids returned by this adapter, if any + * @property {object|null} fledgeAuctionConfigs Optional FLEDGE response, as a map of impid -> auction_config + */ + /** * @typedef {object} ServerRequest * @@ -226,6 +233,17 @@ export function newBidder(spec) { onTimelyResponse(spec.code); responses.push(resp) }, + /** Process eventual BidderAuctionResponse.fledgeAuctionConfig field in response. + * @param {Array} fledgeAuctionConfigs + */ + onFledgeAuctionConfigs: (fledgeAuctionConfigs) => { + fledgeAuctionConfigs.forEach((fledgeAuctionConfig) => { + const bidRequest = bidRequestMap[fledgeAuctionConfig.bidId]; + if (bidRequest) { + addComponentAuction(bidRequest, fledgeAuctionConfig); + } + }); + }, // If the server responds with an error, there's not much we can do beside logging. onError: (errorMessage, error) => { onTimelyResponse(spec.code); @@ -257,10 +275,11 @@ export function newBidder(spec) { }); function isInvalidAlternateBidder(responseBidder, requestBidder) { - let allowAlternateBidderCodes = bidderSettings.get(requestBidder, 'allowAlternateBidderCodes'); + let allowAlternateBidderCodes = bidderSettings.get(requestBidder, 'allowAlternateBidderCodes') || false; let alternateBiddersList = bidderSettings.get(requestBidder, 'allowedAlternateBidderCodes'); if (!!responseBidder && !!requestBidder && requestBidder !== responseBidder) { - if ((allowAlternateBidderCodes !== undefined && !allowAlternateBidderCodes) || (isArray(alternateBiddersList) && (alternateBiddersList[0] !== '*' && !alternateBiddersList.includes(responseBidder)))) { + alternateBiddersList = isArray(alternateBiddersList) ? alternateBiddersList.map(val => val.trim().toLowerCase()).filter(val => !!val).filter(uniques) : alternateBiddersList; + if (!allowAlternateBidderCodes || (isArray(alternateBiddersList) && (alternateBiddersList[0] !== '*' && !alternateBiddersList.includes(responseBidder)))) { return true; } } @@ -295,7 +314,7 @@ export function newBidder(spec) { * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse * @param onCompletion {function()} invoked once when all bid requests have been processed */ -export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onError, onBid, onCompletion}) { +export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onFledgeAuctionConfigs, onError, onBid, onCompletion}) { let requests = spec.buildRequests(bids, bidderRequest); if (!requests || requests.length === 0) { onCompletion(); @@ -323,15 +342,23 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe }; onResponse(response); - let bids; try { - bids = spec.interpretResponse(response, request); + response = spec.interpretResponse(response, request); } catch (err) { logError(`Bidder ${spec.code} failed to interpret the server's response. Continuing without bids`, null, err); requestDone(); return; } + let bids; + // Extract additional data from a structured {BidderAuctionResponse} response + if (response && isArray(response.fledgeAuctionConfigs)) { + onFledgeAuctionConfigs(response.fledgeAuctionConfigs); + bids = response.bids; + } else { + bids = response; + } + if (bids) { if (isArray(bids)) { bids.forEach(onBid); @@ -418,6 +445,10 @@ export const registerSyncInner = hook('async', function(spec, responses, gdprCon } }, 'registerSyncs') +export const addComponentAuction = hook('sync', (_bidRequest, fledgeAuctionConfig) => { + logInfo(`bidderFactory.addComponentAuction`, fledgeAuctionConfig); +}, 'addComponentAuction') + export function preloadBidderMappingFile(fn, adUnits) { if (!config.getConfig('adpod.brandCategoryExclusion')) { return fn.call(this, adUnits); @@ -542,7 +573,7 @@ export function isValid(adUnitCode, bid, {index = auctionManager.index} = {}) { return false; } - if (bid.mediaType === 'native' && !nativeBidIsValid(bid, {index})) { + if (FEATURES.NATIVE && bid.mediaType === 'native' && !nativeBidIsValid(bid, {index})) { logError(errorMessage('Native bid missing some required properties.')); return false; } diff --git a/src/adloader.js b/src/adloader.js index b167079d488..6b7427d3e52 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,21 +1,25 @@ import {includes} from './polyfill.js'; -import { logError, logWarn, insertElement } from './utils.js'; +import { logError, logWarn, insertElement, setScriptAttributes } from './utils.js'; const _requestCache = new WeakMap(); // The below list contains modules or vendors whom Prebid allows to load external JS. const _approvedLoadExternalJSList = [ + 'debugging', 'adloox', 'criteo', 'outstream', 'adagio', + 'spotx', 'browsi', 'brandmetrics', 'justtag', + 'tncId', 'akamaidap', 'ftrackId', 'inskin', 'hadron', - 'medianet' + 'medianet', + 'improvedigital' ] /** @@ -25,8 +29,9 @@ const _approvedLoadExternalJSList = [ * @param {string} moduleCode bidderCode or module code of the module requesting this resource * @param {function} [callback] callback function to be called after the script is loaded * @param {Document} [doc] the context document, in which the script will be loaded, defaults to loaded document + * @param {object} an object of attributes to be added to the script with setAttribute by [key] and [value]; Only the attributes passed in the first request of a url will be added. */ -export function loadExternalScript(url, moduleCode, callback, doc) { +export function loadExternalScript(url, moduleCode, callback, doc, attributes) { if (!moduleCode || !url) { logError('cannot load external script without url and moduleCode'); return; @@ -75,9 +80,9 @@ export function loadExternalScript(url, moduleCode, callback, doc) { } catch (e) { logError('Error executing callback', 'adloader.js:loadExternalScript', e); } - }, doc); + }, doc, attributes); - function requestResource(tagSrc, callback, doc) { + function requestResource(tagSrc, callback, doc, attributes) { if (!doc) { doc = document; } @@ -105,6 +110,10 @@ export function loadExternalScript(url, moduleCode, callback, doc) { jptScript.src = tagSrc; + if (attributes) { + setScriptAttributes(jptScript, attributes); + } + // add the new script tag to the page insertElement(jptScript, doc); diff --git a/src/adserver.js b/src/adserver.js index 8d99b29c3ef..db7aaaa1dc8 100644 --- a/src/adserver.js +++ b/src/adserver.js @@ -1,61 +1,5 @@ -import { formatQS } from './utils.js'; -import { targeting } from './targeting.js'; import {hook} from './hook.js'; -// Adserver parent class -const AdServer = function(attr) { - this.name = attr.adserver; - this.code = attr.code; - this.getWinningBidByCode = function() { - return targeting.getWinningBids(this.code)[0]; - }; -}; - -// DFP ad server -// TODO: this seems to be unused? -export function dfpAdserver(options, urlComponents) { - var adserver = new AdServer(options); - adserver.urlComponents = urlComponents; - - var dfpReqParams = { - 'env': 'vp', - 'gdfp_req': '1', - 'impl': 's', - 'unviewed_position_start': '1' - }; - - var dfpParamsWithVariableValue = ['output', 'iu', 'sz', 'url', 'correlator', 'description_url', 'hl']; - - var getCustomParams = function(targeting) { - return encodeURIComponent(formatQS(targeting)); - }; - - adserver.appendQueryParams = function() { - var bid = adserver.getWinningBidByCode(); - if (bid) { - this.urlComponents.search.description_url = encodeURIComponent(bid.vastUrl); - this.urlComponents.search.cust_params = getCustomParams(bid.adserverTargeting); - this.urlComponents.search.correlator = Date.now(); - } - }; - - adserver.verifyAdserverTag = function() { - for (var key in dfpReqParams) { - if (!this.urlComponents.search.hasOwnProperty(key) || this.urlComponents.search[key] !== dfpReqParams[key]) { - return false; - } - } - for (var i in dfpParamsWithVariableValue) { - if (!this.urlComponents.search.hasOwnProperty(dfpParamsWithVariableValue[i])) { - return false; - } - } - return true; - }; - - return adserver; -}; - /** * return the GAM PPID, if available (eid for the userID configured with `userSync.ppidSource`) */ diff --git a/src/auction.js b/src/auction.js index 03ddd25c048..4d6462f2b6b 100644 --- a/src/auction.js +++ b/src/auction.js @@ -76,6 +76,7 @@ import {bidderSettings} from './bidderSettings.js'; import * as events from './events.js' import adapterManager from './adapterManager.js'; import CONSTANTS from './constants.json'; +import {GreedyPromise} from './utils/promise.js'; const { syncUsers } = userSync; @@ -93,6 +94,14 @@ const outstandingRequests = {}; const sourceInfo = {}; const queuedCalls = []; +/** + * Clear global state for tests + */ +export function resetAuctionState() { + queuedCalls.length = 0; + [outstandingRequests, sourceInfo].forEach((ob) => Object.keys(ob).forEach((k) => { delete ob[k] })); +} + /** * Creates new auction instance * @@ -103,10 +112,11 @@ const queuedCalls = []; * @param {number} requestConfig.cbTimeout * @param {Array.} requestConfig.labels * @param {string} requestConfig.auctionId - * + * @param {{global: {}, bidder: {}}} ortb2Fragments first party data, separated into global + * (from getConfig('ortb2') + requestBids({ortb2})) and bidder (a map from bidderCode to ortb2) * @returns {Auction} auction instance */ -export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, auctionId}) { +export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, auctionId, ortb2Fragments}) { let _adUnits = adUnits; let _labels = labels; let _adUnitCodes = adUnitCodes; @@ -215,7 +225,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a _auctionStatus = AUCTION_STARTED; _auctionStart = Date.now(); - let bidRequests = adapterManager.makeBidRequests(_adUnits, _auctionStart, _auctionId, _timeout, _labels); + let bidRequests = adapterManager.makeBidRequests(_adUnits, _auctionStart, _auctionId, _timeout, _labels, ortb2Fragments); logInfo(`Bids Requested for Auction with id: ${_auctionId}`, bidRequests); if (bidRequests.length < 1) { @@ -272,7 +282,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a } } } - }, _timeout, onTimelyResponse); + }, _timeout, onTimelyResponse, ortb2Fragments); } }; @@ -324,11 +334,11 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a function addWinningBid(winningBid) { _winningBids = _winningBids.concat(winningBid); - adapterManager.callBidWonBidder(winningBid.bidder, winningBid, adUnits); + adapterManager.callBidWonBidder(winningBid.adapterCode || winningBid.bidder, winningBid, adUnits); } function setBidTargeting(bid) { - adapterManager.callSetTargetingBidder(bid.bidder, bid); + adapterManager.callSetTargetingBidder(bid.adapterCode || bid.bidder, bid); } return { @@ -348,8 +358,8 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a getBidRequests: () => _bidderRequests, getBidsReceived: () => _bidsReceived, getNoBids: () => _noBids, - - } + getFPD: () => ortb2Fragments + }; } export const addBidResponse = hook('sync', function(adUnitCode, bid) { @@ -375,9 +385,9 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM function waitFor(requestId, result) { if (ready[requestId] == null) { - ready[requestId] = Promise.resolve(); + ready[requestId] = GreedyPromise.resolve(); } - ready[requestId] = ready[requestId].then(() => Promise.resolve(result).catch(() => {})) + ready[requestId] = ready[requestId].then(() => GreedyPromise.resolve(result).catch(() => {})) } function guard(bidderRequest, fn) { @@ -389,9 +399,9 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM const wait = ready[bidderRequest.bidderRequestId]; const orphanWait = ready['']; // also wait for "orphan" responses that are not associated with any request if ((wait != null || orphanWait != null) && timeRemaining > 0) { - Promise.race([ - new Promise((resolve) => setTimeout(resolve, timeRemaining)), - Promise.resolve(orphanWait).then(() => wait) + GreedyPromise.race([ + GreedyPromise.timeout(timeRemaining), + GreedyPromise.resolve(orphanWait).then(() => wait) ]).then(fn); } else { fn(); @@ -488,8 +498,9 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au transactionId: bidResponse.transactionId }), 'video'); const context = videoMediaType && deepAccess(videoMediaType, 'context'); + const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); - if (config.getConfig('cache.url') && context !== OUTSTREAM) { + if (config.getConfig('cache.url') && (useCacheKey || context !== OUTSTREAM)) { if (!bidResponse.videoCacheKey || config.getConfig('cache.ignoreBidderCacheKey')) { addBid = false; callPrebidCache(auctionInstance, bidResponse, afterBidAdded, videoMediaType); @@ -504,28 +515,71 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au } } -export const callPrebidCache = hook('async', function(auctionInstance, bidResponse, afterBidAdded, videoMediaType) { - store([bidResponse], function (error, cacheIds) { - if (error) { - logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); - - doCallbacksIfTimedout(auctionInstance, bidResponse); - } else { - if (cacheIds[0].uuid === '') { - logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); +const storeInCache = (batch) => { + store(batch.map(entry => entry.bidResponse), function (error, cacheIds) { + cacheIds.forEach((cacheId, i) => { + const { auctionInstance, bidResponse, afterBidAdded } = batch[i]; + if (error) { + logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); doCallbacksIfTimedout(auctionInstance, bidResponse); } else { - bidResponse.videoCacheKey = cacheIds[0].uuid; + if (cacheId.uuid === '') { + logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); + + doCallbacksIfTimedout(auctionInstance, bidResponse); + } else { + bidResponse.videoCacheKey = cacheId.uuid; - if (!bidResponse.vastUrl) { - bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); + if (!bidResponse.vastUrl) { + bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); + } + addBidToAuction(auctionInstance, bidResponse); + afterBidAdded(); } - addBidToAuction(auctionInstance, bidResponse); - afterBidAdded(); } - } + }); }); +}; + +let batchSize, batchTimeout; +config.getConfig('cache', (cacheConfig) => { + batchSize = typeof cacheConfig.cache.batchSize === 'number' && cacheConfig.cache.batchSize > 0 + ? cacheConfig.cache.batchSize + : 1; + batchTimeout = typeof cacheConfig.cache.batchTimeout === 'number' && cacheConfig.cache.batchTimeout > 0 + ? cacheConfig.cache.batchTimeout + : 0; +}); + +export const batchingCache = (timeout = setTimeout, cache = storeInCache) => { + let batches = [[]]; + let debouncing = false; + const noTimeout = cb => cb(); + + return function(auctionInstance, bidResponse, afterBidAdded) { + const batchFunc = batchTimeout > 0 ? timeout : noTimeout; + if (batches[batches.length - 1].length >= batchSize) { + batches.push([]); + } + + batches[batches.length - 1].push({auctionInstance, bidResponse, afterBidAdded}); + + if (!debouncing) { + debouncing = true; + batchFunc(() => { + batches.forEach(cache); + batches = [[]]; + debouncing = false; + }, batchTimeout); + } + } +}; + +const batchAndStore = batchingCache(); + +export const callPrebidCache = hook('async', function(auctionInstance, bidResponse, afterBidAdded, videoMediaType) { + batchAndStore(auctionInstance, bidResponse, afterBidAdded); }, 'callPrebidCache'); // Postprocess the bids so that all the universal properties exist, no matter which bidder they came from. @@ -556,7 +610,7 @@ function getPreparedBidForAuction({adUnitCode, bid, auctionId}, {index = auction // a publisher can also define a renderer for a mediaType const bidObjectMediaType = bidObject.mediaType; - const mediaTypes = index.getMediaTypes(bidObject) + const mediaTypes = index.getMediaTypes(bidObject); const bidMediaType = mediaTypes && mediaTypes[bidObjectMediaType]; var mediaTypeRenderer = bidMediaType && bidMediaType.renderer; @@ -756,7 +810,7 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj, {index = aucti } // set native key value targeting - if (custBidObj['native']) { + if (FEATURES.NATIVE && custBidObj['native']) { keyValues = Object.assign({}, keyValues, getNativeTargeting(custBidObj)); } diff --git a/src/auctionManager.js b/src/auctionManager.js index 5abcf0f9129..6d60f28946c 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -87,8 +87,8 @@ export function newAuctionManager() { .filter(uniques); }; - auctionManager.createAuction = function({ adUnits, adUnitCodes, callback, cbTimeout, labels, auctionId }) { - const auction = newAuction({ adUnits, adUnitCodes, callback, cbTimeout, labels, auctionId }); + auctionManager.createAuction = function(opts) { + const auction = newAuction(opts); _addAuction(auction); return auction; }; diff --git a/src/bidfactory.js b/src/bidfactory.js index 95d69cf0adb..4c2e4cf3ffb 100644 --- a/src/bidfactory.js +++ b/src/bidfactory.js @@ -59,7 +59,7 @@ function Bid(statusCode, {src = 'client', bidder = '', bidId, transactionId, auc transactionId: this.transactionId, auctionId: this.auctionId } - } + }; } // Bid factory function. diff --git a/src/config.js b/src/config.js index 484051b2362..0922a2a2bea 100644 --- a/src/config.js +++ b/src/config.js @@ -22,7 +22,6 @@ import CONSTANTS from './constants.json'; const DEFAULT_DEBUG = getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; const DEFAULT_BIDDER_TIMEOUT = 3000; -const DEFAULT_PUBLISHER_DOMAIN = window.location.origin; const DEFAULT_ENABLE_SEND_ALL_BIDS = true; const DEFAULT_DISABLE_AJAX_TIMEOUT = false; const DEFAULT_BID_CACHE = false; @@ -86,12 +85,12 @@ export function newConfig() { this._bidderTimeout = val; }, - // domain where prebid is running for cross domain iframe communication - _publisherDomain: DEFAULT_PUBLISHER_DOMAIN, + _publisherDomain: null, get publisherDomain() { return this._publisherDomain; }, set publisherDomain(val) { + logWarn('publisherDomain is deprecated and has no effect since v7 - use pageUrl instead') this._publisherDomain = val; }, @@ -313,42 +312,52 @@ export function newConfig() { return Object.assign({}, config); } - /* - * Returns the configuration object if called without parameters, - * or single configuration property if given a string matching a configuration - * property name. Allows deep access e.g. getConfig('currency.adServerCurrency') - * - * If called with callback parameter, or a string and a callback parameter, - * subscribes to configuration updates. See `subscribe` function for usage. - * - * The object returned is a deepClone of the `config` property. - */ - function readConfig(...args) { - if (args.length <= 1 && typeof args[0] !== 'function') { - const option = args[0]; - const configClone = deepClone(_getConfig()); - return option ? deepAccess(configClone, option) : configClone; - } - - return subscribe(...args); + function _getRestrictedConfig() { + // This causes reading 'ortb2' to throw an error; with prebid 7, that will almost + // always be the incorrect way to access FPD configuration (https://github.com/prebid/Prebid.js/issues/7651) + // code that needs the ortb2 config should explicitly use `getAnyConfig` + // TODO: this is meant as a temporary tripwire to catch inadvertent use of `getConfig('ortb')` as we transition. + // It should be removed once the risk of that happening is low enough. + const conf = _getConfig(); + Object.defineProperty(conf, 'ortb2', { + get: function () { + throw new Error('invalid access to \'orbt2\' config - use request parameters instead'); + } + }); + return conf; } - /* - * Returns configuration object if called without parameters, - * or single configuration property if given a string matching a configuration - * property name. Allows deep access e.g. getConfig('currency.adServerCurrency') - * - * If called with callback parameter, or a string and a callback parameter, - * subscribes to configuration updates. See `subscribe` function for usage. - */ - function getConfig(...args) { - if (args.length <= 1 && typeof args[0] !== 'function') { - const option = args[0]; - return option ? deepAccess(_getConfig(), option) : _getConfig(); - } + const [getAnyConfig, getConfig] = [_getConfig, _getRestrictedConfig].map(accessor => { + /* + * Returns configuration object if called without parameters, + * or single configuration property if given a string matching a configuration + * property name. Allows deep access e.g. getConfig('currency.adServerCurrency') + * + * If called with callback parameter, or a string and a callback parameter, + * subscribes to configuration updates. See `subscribe` function for usage. + */ + return function getConfig(...args) { + if (args.length <= 1 && typeof args[0] !== 'function') { + const option = args[0]; + return option ? deepAccess(accessor(), option) : _getConfig(); + } - return subscribe(...args); - } + return subscribe(...args); + } + }) + + const [readConfig, readAnyConfig] = [getConfig, getAnyConfig].map(wrapee => { + /* + * Like getConfig, except that it returns a deepClone of the result. + */ + return function readConfig(...args) { + let res = wrapee(...args); + if (res && typeof res === 'object') { + res = deepClone(res); + } + return res; + } + }) /** * Internal API for modules (such as prebid-server) that might need access to all bidder config @@ -357,119 +366,6 @@ export function newConfig() { return bidderConfig; } - /** - * Returns backwards compatible FPD data for modules - */ - function getLegacyFpd(obj) { - if (typeof obj !== 'object') return; - - let duplicate = {}; - - Object.keys(obj).forEach((type) => { - let prop = (type === 'site') ? 'context' : type; - duplicate[prop] = (prop === 'context' || prop === 'user') ? Object.keys(obj[type]).filter(key => key !== 'data').reduce((result, key) => { - if (key === 'ext') { - mergeDeep(result, obj[type][key]); - } else { - mergeDeep(result, {[key]: obj[type][key]}); - } - - return result; - }, {}) : obj[type]; - }); - - return duplicate; - } - - /** - * Returns backwards compatible FPD data for modules - */ - function getLegacyImpFpd(obj) { - if (typeof obj !== 'object') return; - - let duplicate = {}; - - if (deepAccess(obj, 'ext.data')) { - Object.keys(obj.ext.data).forEach((key) => { - if (key === 'pbadslot') { - mergeDeep(duplicate, {context: {pbAdSlot: obj.ext.data[key]}}); - } else if (key === 'adserver') { - mergeDeep(duplicate, {context: {adServer: obj.ext.data[key]}}); - } else { - mergeDeep(duplicate, {context: {data: {[key]: obj.ext.data[key]}}}); - } - }); - } - - return duplicate; - } - - /** - * Copy FPD over to OpenRTB standard format in config - */ - function convertFpd(opt) { - let duplicate = {}; - - Object.keys(opt).forEach((type) => { - let prop = (type === 'context') ? 'site' : type; - duplicate[prop] = (prop === 'site' || prop === 'user') ? Object.keys(opt[type]).reduce((result, key) => { - if (key === 'data') { - mergeDeep(result, {ext: {data: opt[type][key]}}); - } else { - mergeDeep(result, {[key]: opt[type][key]}); - } - - return result; - }, {}) : opt[type]; - }); - - return duplicate; - } - - /** - * Copy Impression FPD over to OpenRTB standard format in config - * Only accepts bid level context.data values with pbAdSlot and adServer exceptions - */ - function convertImpFpd(opt) { - let duplicate = {}; - - Object.keys(opt).filter(prop => prop === 'context').forEach((type) => { - Object.keys(opt[type]).forEach((key) => { - if (key === 'data') { - mergeDeep(duplicate, {ext: {data: opt[type][key]}}); - } else { - if (typeof opt[type][key] === 'object' && !Array.isArray(opt[type][key])) { - Object.keys(opt[type][key]).forEach(data => { - mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: {[data.toLowerCase()]: opt[type][key][data]}}}}); - }); - } else { - mergeDeep(duplicate, {ext: {data: {[key.toLowerCase()]: opt[type][key]}}}); - } - } - }); - }); - - return duplicate; - } - - /** - * Copy FPD over to OpenRTB standard format in each adunit - */ - function convertAdUnitFpd(arr) { - let convert = []; - - arr.forEach((adunit) => { - if (adunit.fpd) { - (adunit['ortb2Imp']) ? mergeDeep(adunit['ortb2Imp'], convertImpFpd(adunit.fpd)) : adunit['ortb2Imp'] = convertImpFpd(adunit.fpd); - convert.push((({ fpd, ...duplicate }) => duplicate)(adunit)); - } else { - convert.push(adunit); - } - }); - - return convert; - } - /* * Sets configuration given an object containing key-value pairs and calls * listeners that were added by the `subscribe` function @@ -484,14 +380,17 @@ export function newConfig() { let topicalConfig = {}; topics.forEach(topic => { - let prop = (topic === 'fpd') ? 'ortb2' : topic; - let option = (topic === 'fpd') ? convertFpd(options[topic]) : options[topic]; + let option = options[topic]; - if (isPlainObject(defaults[prop]) && isPlainObject(option)) { - option = Object.assign({}, defaults[prop], option); + if (isPlainObject(defaults[topic]) && isPlainObject(option)) { + option = Object.assign({}, defaults[topic], option); } - topicalConfig[prop] = config[prop] = option; + try { + topicalConfig[topic] = config[topic] = option; + } catch (e) { + logWarn(`Cannot set config for property ${topic} : `, e) + } }); callSubscribers(topicalConfig); @@ -519,6 +418,8 @@ export function newConfig() { * updates when specific properties are updated by passing a topic string as * the first parameter. * + * If `options.init` is true, the listener will be immediately called with the current options. + * * Returns an `unsubscribe` function for removing the subscriber from the * set of listeners * @@ -532,8 +433,9 @@ export function newConfig() { * // unsubscribe * const unsubscribe = subscribe(...); * unsubscribe(); // no longer listening + * */ - function subscribe(topic, listener) { + function subscribe(topic, listener, options = {}) { let callback = listener; if (typeof topic !== 'string') { @@ -541,6 +443,7 @@ export function newConfig() { // meaning it gets called for any config change callback = topic; topic = ALL_TOPICS; + options = listener || {}; } if (typeof callback !== 'function') { @@ -551,6 +454,15 @@ export function newConfig() { const nl = { topic, callback }; listeners.push(nl); + if (options.init) { + if (topic === ALL_TOPICS) { + callback(getConfig()); + } else { + // eslint-disable-next-line standard/no-callback-literal + callback({[topic]: getConfig(topic)}); + } + } + // save and call this function to remove the listener return function unsubscribe() { listeners.splice(listeners.indexOf(nl), 1); @@ -584,14 +496,13 @@ export function newConfig() { bidderConfig[bidder] = {}; } Object.keys(config.config).forEach(topic => { - let prop = (topic === 'fpd') ? 'ortb2' : topic; - let option = (topic === 'fpd') ? convertFpd(config.config[topic]) : config.config[topic]; + let option = config.config[topic]; if (isPlainObject(option)) { const func = mergeFlag ? mergeDeep : Object.assign; - bidderConfig[bidder][prop] = func({}, bidderConfig[bidder][prop] || {}, option); + bidderConfig[bidder][topic] = func({}, bidderConfig[bidder][topic] || {}, option); } else { - bidderConfig[bidder][prop] = option; + bidderConfig[bidder][topic] = option; } }); }); @@ -618,11 +529,7 @@ export function newConfig() { return; } - const mergedConfig = Object.keys(obj).reduce((accum, key) => { - const prevConf = _getConfig(key)[key] || {}; - accum[key] = mergeDeep(prevConf, obj[key]); - return accum; - }, {}); + const mergedConfig = mergeDeep(_getConfig(), obj); setConfig({ ...mergedConfig }); return mergedConfig; @@ -669,7 +576,9 @@ export function newConfig() { getCurrentBidder, resetBidder, getConfig, + getAnyConfig, readConfig, + readAnyConfig, setConfig, mergeConfig, setDefaults, @@ -679,9 +588,6 @@ export function newConfig() { setBidderConfig, getBidderConfig, mergeBidderConfig, - convertAdUnitFpd, - getLegacyFpd, - getLegacyImpFpd }; } diff --git a/src/consentHandler.js b/src/consentHandler.js index a56d06c8c90..861a9894a2c 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -1,10 +1,10 @@ import {isStr, timestamp} from './utils.js'; +import {defer, GreedyPromise} from './utils/promise.js'; export class ConsentHandler { #enabled; #data; - #promise; - #resolve; + #defer; #ready; generatedTime; @@ -12,17 +12,17 @@ export class ConsentHandler { this.reset(); } + #resolve(data) { + this.#ready = true; + this.#data = data; + this.#defer.resolve(data); + } + /** * reset this handler (mainly for tests) */ reset() { - this.#promise = new Promise((resolve) => { - this.#resolve = (data) => { - this.#ready = true; - this.#data = data; - resolve(data); - }; - }); + this.#defer = defer(); this.#enabled = false; this.#data = null; this.#ready = false; @@ -56,12 +56,12 @@ export class ConsentHandler { */ get promise() { if (this.#ready) { - return Promise.resolve(this.#data); + return GreedyPromise.resolve(this.#data); } if (!this.#enabled) { this.#resolve(null); } - return this.#promise; + return this.#defer.promise; } setConsentData(data, time = timestamp()) { diff --git a/src/constants.json b/src/constants.json index 94696184e31..ad57fb45c4f 100644 --- a/src/constants.json +++ b/src/constants.json @@ -46,7 +46,7 @@ "STALE_RENDER": "staleRender", "BILLABLE_EVENT": "billableEvent" }, - "AD_RENDER_FAILED_REASON" : { + "AD_RENDER_FAILED_REASON": { "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument", "NO_AD": "noAd", "EXCEPTION": "exception", @@ -75,7 +75,7 @@ "UUID": "hb_uuid", "CACHE_ID": "hb_cache_id", "CACHE_HOST": "hb_cache_host", - "ADOMAIN" : "hb_adomain" + "ADOMAIN": "hb_adomain" }, "DEFAULT_TARGETING_KEYS": { "BIDDER": "hb_bidder", @@ -109,14 +109,54 @@ "rendererUrl": "hb_renderer_url", "adTemplate": "hb_adTemplate" }, - "S2S" : { - "SRC" : "s2s", - "DEFAULT_ENDPOINT" : "https://prebid.adnxs.com/pbs/v1/openrtb2/auction", + "S2S": { + "SRC": "s2s", + "DEFAULT_ENDPOINT": "https://prebid.adnxs.com/pbs/v1/openrtb2/auction", "SYNCED_BIDDERS_KEY": "pbjsSyncs" }, - "BID_STATUS" : { + "BID_STATUS": { "BID_TARGETING_SET": "targetingSet", "RENDERED": "rendered", "BID_REJECTED": "bidRejected" - } + }, + "PREBID_NATIVE_DATA_KEYS_TO_ORTB": { + "body": "desc", + "body2": "desc2", + "sponsoredBy": "sponsored", + "cta": "ctatext", + "rating": "rating", + "address": "address", + "downloads": "downloads", + "likes": "likes", + "phone": "phone", + "price": "price", + "salePrice": "saleprice", + "displayUrl": "displayurl" + }, + "NATIVE_ASSET_TYPES": { + "sponsored": 1, + "desc": 2, + "rating": 3, + "likes": 4, + "downloads": 5, + "price": 6, + "saleprice": 7, + "phone": 8, + "address": 9, + "desc2": 10, + "displayurl": 11, + "ctatext": 12 + }, + "NATIVE_IMAGE_TYPES": { + "ICON": 1, + "MAIN": 3 + }, + "NATIVE_KEYS_THAT_ARE_NOT_ASSETS": [ + "privacyLink", + "clickUrl", + "sendTargetingKeys", + "adTemplate", + "rendererUrl", + "type" + ] } diff --git a/src/debugging.js b/src/debugging.js index 810cf4b432a..f5d13d1a134 100644 --- a/src/debugging.js +++ b/src/debugging.js @@ -1,165 +1,94 @@ import {config} from './config.js'; -import {addBidderRequests, addBidResponse} from './auction.js'; -import {hook} from './hook.js'; -import {prefixLog} from './utils.js'; +import {getHook, hook} from './hook.js'; +import {getGlobal} from './prebidGlobal.js'; +import {logMessage, prefixLog} from './utils.js'; +import {createBid} from './bidfactory.js'; +import {loadExternalScript} from './adloader.js'; +import {GreedyPromise} from './utils/promise.js'; -const {logWarn, logMessage} = prefixLog('DEBUG:'); +export const DEBUG_KEY = '__$$PREBID_GLOBAL$$_debugging__'; -const OVERRIDE_KEY = '$$PREBID_GLOBAL$$:debugging'; - -export let addBidResponseBound; -export let addBidderRequestsBound; - -export const onEnableOverrides = [ - (overrides) => { - removeHooks(); - addHooks(overrides); - } -]; -export const onDisableOverrides = [ - removeHooks -]; - -function addHooks(overrides) { - addBidResponseBound = addBidResponseHook.bind(overrides); - addBidResponse.before(addBidResponseBound, 5); - - addBidderRequestsBound = addBidderRequestsHook.bind(overrides); - addBidderRequests.before(addBidderRequestsBound, 5); +function isDebuggingInstalled() { + return getGlobal().installedModules.includes('debugging'); } -function removeHooks() { - addBidResponse.getHooks({hook: addBidResponseBound}).remove(); - addBidderRequests.getHooks({hook: addBidderRequestsBound}).remove(); -} - -export function enableOverrides(overrides, fromSession = false) { - config.setConfig({'debug': true}); - onEnableOverrides.forEach((fn) => fn(overrides)); - logMessage(`bidder overrides enabled${fromSession ? ' from session' : ''}`); +function loadScript(url) { + return new GreedyPromise((resolve) => { + loadExternalScript(url, 'debugging', resolve); + }); } -export function disableOverrides() { - onDisableOverrides.forEach((fn) => fn()); - logMessage('bidder overrides disabled'); +export function debuggingModuleLoader({alreadyInstalled = isDebuggingInstalled, script = loadScript} = {}) { + let loading = null; + return function () { + if (loading == null) { + loading = new GreedyPromise((resolve, reject) => { + // run this in a 0-delay timeout to give installedModules time to be populated + setTimeout(() => { + if (alreadyInstalled()) { + resolve(); + } else { + const url = '$$PREBID_DIST_URL_BASE$$debugging-standalone.js'; + logMessage(`Debugging module not installed, loading it from "${url}"...`); + getGlobal()._installDebugging = true; + script(url).then(() => { + getGlobal()._installDebugging({DEBUG_KEY, hook, config, createBid, logger: prefixLog('DEBUG:')}); + }).then(resolve, reject); + } + }); + }) + } + return loading; + } } -/** - * @param {{bidder:string, adUnitCode:string}} overrideObj - * @param {string} bidderCode - * @param {string} adUnitCode - * @returns {boolean} - */ -export function bidExcluded(overrideObj, bidderCode, adUnitCode) { - if (overrideObj.bidder && overrideObj.bidder !== bidderCode) { - return true; +export function debuggingControls({load = debuggingModuleLoader(), hook = getHook('requestBids')} = {}) { + let promise = null; + let enabled = false; + function waitForDebugging(next, ...args) { + return (promise || GreedyPromise.resolve()).then(() => next.apply(this, args)) } - if (overrideObj.adUnitCode && overrideObj.adUnitCode !== adUnitCode) { - return true; + function enable() { + if (!enabled) { + promise = load(); + // set debugging to high priority so that it has the opportunity to mess with most things + hook.before(waitForDebugging, 99); + enabled = true; + } } - return false; -} - -/** - * @param {string[]} bidders - * @param {string} bidderCode - * @returns {boolean} - */ -export function bidderExcluded(bidders, bidderCode) { - return (Array.isArray(bidders) && bidders.indexOf(bidderCode) === -1); -} - -/** - * @param {Object} overrideObj - * @param {Object} bidObj - * @param {Object} bidType - * @returns {Object} bidObj with overridden properties - */ -export function applyBidOverrides(overrideObj, bidObj, bidType) { - return Object.keys(overrideObj).filter(key => (['adUnitCode', 'bidder'].indexOf(key) === -1)).reduce(function(result, key) { - logMessage(`bidder overrides changed '${result.adUnitCode}/${result.bidderCode}' ${bidType}.${key} from '${result[key]}.js' to '${overrideObj[key]}'`); - result[key] = overrideObj[key]; - result.isDebug = true; - return result; - }, bidObj); -} - -export function addBidResponseHook(next, adUnitCode, bid) { - const overrides = this; - - if (bidderExcluded(overrides.bidders, bid.bidderCode)) { - logWarn(`bidder '${bid.bidderCode}' excluded from auction by bidder overrides`); - return; + function disable() { + hook.getHooks({hook: waitForDebugging}).remove(); + enabled = false; } - - if (Array.isArray(overrides.bids)) { - overrides.bids.forEach(function(overrideBid) { - if (!bidExcluded(overrideBid, bid.bidderCode, adUnitCode)) { - applyBidOverrides(overrideBid, bid, 'bidder'); - } - }); + function reset() { + promise = null; + disable(); } - - next(adUnitCode, bid); + return {enable, disable, reset}; } -export function addBidderRequestsHook(next, bidderRequests) { - const overrides = this; +const ctl = debuggingControls(); +export const reset = ctl.reset; - const includedBidderRequests = bidderRequests.filter(function (bidderRequest) { - if (bidderExcluded(overrides.bidders, bidderRequest.bidderCode)) { - logWarn(`bidRequest '${bidderRequest.bidderCode}' excluded from auction by bidder overrides`); - return false; - } - return true; - }); - - if (Array.isArray(overrides.bidRequests)) { - includedBidderRequests.forEach(function(bidderRequest) { - overrides.bidRequests.forEach(function(overrideBid) { - bidderRequest.bids.forEach(function(bid) { - if (!bidExcluded(overrideBid, bidderRequest.bidderCode, bid.adUnitCode)) { - applyBidOverrides(overrideBid, bid, 'bidRequest'); - } - }); - }); - }); - } - - next(includedBidderRequests); -} +export function loadSession() { + let storage = null; + try { + storage = window.sessionStorage; + } catch (e) {} -export const saveDebuggingConfig = hook('sync', function (debugConfig, {sessionStorage = window.sessionStorage} = {}) { - if (!debugConfig.enabled) { - try { - sessionStorage.removeItem(OVERRIDE_KEY); - } catch (e) {} - } else { + if (storage !== null) { + let debugging = ctl; + let config = null; try { - sessionStorage.setItem(OVERRIDE_KEY, JSON.stringify(debugConfig)); + config = storage.getItem(DEBUG_KEY); } catch (e) {} - } -}); - -export function getConfig(debugging, {sessionStorage = window.sessionStorage} = {}) { - saveDebuggingConfig(debugging, {sessionStorage}); - if (!debugging.enabled) { - disableOverrides(); - } else { - enableOverrides(debugging); + if (config !== null) { + // just make sure the module runs; it will take care of parsing the config (and disabling itself if necessary) + debugging.enable(); + } } } -config.getConfig('debugging', ({debugging}) => getConfig(debugging)); - -export function sessionLoader(storage) { - let overrides; - try { - storage = storage || window.sessionStorage; - overrides = JSON.parse(storage.getItem(OVERRIDE_KEY)); - } catch (e) { - } - if (overrides) { - enableOverrides(overrides, true); - } -} +config.getConfig('debugging', function ({debugging}) { + debugging?.enabled ? ctl.enable() : ctl.disable(); +}); diff --git a/src/hook.js b/src/hook.js index c3f897a6bca..3f01114935d 100644 --- a/src/hook.js +++ b/src/hook.js @@ -1,11 +1,11 @@ import funHooks from 'fun-hooks/no-eval/index.js'; -import {promiseControls} from './utils/promise.js'; +import {defer} from './utils/promise.js'; export let hook = funHooks({ ready: funHooks.SYNC | funHooks.ASYNC | funHooks.QUEUE }); -const readyCtl = promiseControls(); +const readyCtl = defer(); hook.ready = (() => { const ready = hook.ready; return function () { @@ -48,3 +48,14 @@ export function submodule(name, ...args) { next(modules); }); } + +/** + * Copy hook methods (.before, .after, etc) from a given hook to a given wrapper object. + */ +export function wrapHook(hook, wrapper) { + Object.defineProperties( + wrapper, + Object.fromEntries(['before', 'after', 'getHooks', 'removeAll'].map((m) => [m, {get: () => hook[m]}])) + ); + return wrapper; +} diff --git a/src/native.js b/src/native.js index 65ccaf208ee..de77b10f7e6 100644 --- a/src/native.js +++ b/src/native.js @@ -1,7 +1,21 @@ -import { deepAccess, getKeyByValue, insertHtmlIntoIframe, logError, triggerPixel } from './utils.js'; +import { + deepAccess, + deepClone, + getKeyByValue, + insertHtmlIntoIframe, + isArray, + isBoolean, + isInteger, + isNumber, + isPlainObject, + logError, + triggerPixel, + pick +} from './utils.js'; import {includes} from './polyfill.js'; import {auctionManager} from './auctionManager.js'; import CONSTANTS from './constants.json'; +import {NATIVE} from './mediaTypes.js'; export const nativeAdapters = []; @@ -10,6 +24,50 @@ export const NATIVE_TARGETING_KEYS = Object.keys(CONSTANTS.NATIVE_KEYS).map( ); const IMAGE = { + ortb: { + ver: '1.2', + assets: [ + { + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }, + { + required: 1, + id: 2, + title: { + len: 140, + } + }, + { + required: 1, + id: 3, + data: { + type: 1, + } + }, + { + required: 0, + id: 4, + data: { + type: 2, + } + }, + { + required: 0, + id: 5, + img: { + type: 1, + wmin: 20, + hmin: 20, + } + }, + ], + }, image: { required: true }, title: { required: true }, sponsoredBy: { required: true }, @@ -22,6 +80,26 @@ const SUPPORTED_TYPES = { image: IMAGE }; +const { NATIVE_ASSET_TYPES, NATIVE_IMAGE_TYPES, PREBID_NATIVE_DATA_KEYS_TO_ORTB, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS } = CONSTANTS; + +// inverse native maps useful for converting to legacy +const PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE = inverse(PREBID_NATIVE_DATA_KEYS_TO_ORTB); +const NATIVE_ASSET_TYPES_INVERSE = inverse(NATIVE_ASSET_TYPES); + +const TRACKER_METHODS = { + img: 1, + js: 2, + 1: 'img', + 2: 'js' +} + +const TRACKER_EVENTS = { + impression: 1, + 'viewable-mrc50': 2, + 'viewable-mrc100': 3, + 'viewable-video50': 4, +} + /** * Recieves nativeParams from an adUnit. If the params were not of type 'type', * passes them on directly. If they were of type 'type', translate @@ -29,9 +107,12 @@ const SUPPORTED_TYPES = { */ export function processNativeAdUnitParams(params) { if (params && params.type && typeIsSupported(params.type)) { - return SUPPORTED_TYPES[params.type]; + params = SUPPORTED_TYPES[params.type]; } + if (params && params.ortb && !isOpenRTBBidRequestValid(params.ortb)) { + return; + } return params; } @@ -42,8 +123,66 @@ export function decorateAdUnitsWithNativeParams(adUnits) { if (nativeParams) { adUnit.nativeParams = processNativeAdUnitParams(nativeParams); } + if (adUnit.nativeParams) { + adUnit.nativeOrtbRequest = adUnit.nativeParams.ortb || toOrtbNativeRequest(adUnit.nativeParams); + } }); } +export function isOpenRTBBidRequestValid(ortb) { + const assets = ortb.assets; + if (!Array.isArray(assets) || assets.length === 0) { + logError(`assets in mediaTypes.native.ortb is not an array, or it's empty. Assets: `, assets); + return false; + } + + // validate that ids exist, that they are unique and that they are numbers + const ids = assets.map(asset => asset.id); + if (assets.length !== new Set(ids).size || ids.some(id => id !== parseInt(id, 10))) { + logError(`each asset object must have 'id' property, it must be unique and it must be an integer`); + return false; + } + + if (ortb.hasOwnProperty('eventtrackers') && !Array.isArray(ortb.eventtrackers)) { + logError('ortb.eventtrackers is not an array. Eventtrackers: ', ortb.eventtrackers); + return false; + } + + return assets.every(asset => isOpenRTBAssetValid(asset)) +} + +function isOpenRTBAssetValid(asset) { + if (!isPlainObject(asset)) { + logError(`asset must be an object. Provided asset: `, asset); + return false; + } + if (asset.img) { + if (!isNumber(asset.img.w) && !isNumber(asset.img.wmin)) { + logError(`for img asset there must be 'w' or 'wmin' property`); + return false; + } + if (!isNumber(asset.img.h) && !isNumber(asset.img.hmin)) { + logError(`for img asset there must be 'h' or 'hmin' property`); + return false; + } + } else if (asset.title) { + if (!isNumber(asset.title.len)) { + logError(`for title asset there must be 'len' property defined`); + return false; + } + } else if (asset.data) { + if (!isNumber(asset.data.type)) { + logError(`for data asset 'type' property must be a number`); + return false; + } + } else if (asset.video) { + if (!Array.isArray(asset.video.mimes) || !Array.isArray(asset.video.protocols) || + !isNumber(asset.video.minduration) || !isNumber(asset.video.maxduration)) { + logError('video asset is not properly configured'); + return false; + } + } + return true; +} /** * Check if the native type specified in the adUnit is supported by Prebid. @@ -79,24 +218,28 @@ export const hasNonNativeBidder = adUnit => * @return {Boolean} If object is valid */ export function nativeBidIsValid(bid, {index = auctionManager.index} = {}) { - // all native bid responses must define a landing page url - if (!deepAccess(bid, 'native.clickUrl')) { + const adUnit = index.getAdUnit(bid); + if (!adUnit) { return false; } + let ortbRequest = adUnit.nativeOrtbRequest + let ortbResponse = bid.native?.ortb || toOrtbNativeResponse(bid.native, ortbRequest); + return isNativeOpenRTBBidValid(ortbResponse, ortbRequest); +} + +export function isNativeOpenRTBBidValid(bidORTB, bidRequestORTB) { + if (!deepAccess(bidORTB, 'link.url')) { + logError(`native response doesn't have 'link' property. Ortb response: `, bidORTB); return false; } - const requestedAssets = index.getAdUnit(bid).nativeParams; - if (!requestedAssets) { - return true; - } + let requiredAssetIds = bidRequestORTB.assets.filter(asset => asset.required === 1).map(a => a.id); + let returnedAssetIds = bidORTB.assets.map(asset => asset.id); - const requiredAssets = Object.keys(requestedAssets).filter( - key => requestedAssets[key].required - ); - const returnedAssets = Object.keys(bid['native']).filter( - key => bid['native'][key] - ); + const match = requiredAssetIds.every(assetId => includes(returnedAssetIds, assetId)); + if (!match) { + logError(`didn't receive a bid with all required assets. Required ids: ${requiredAssetIds}, but received ids in response: ${returnedAssetIds}`); + } - return requiredAssets.every(asset => includes(returnedAssets, asset)); + return match; } /* @@ -125,20 +268,64 @@ export function nativeBidIsValid(bid, {index = auctionManager.index} = {}) { * fireTrackers(); // fires impressions when creative is loaded * */ -export function fireNativeTrackers(message, adObject) { - let trackers; +export function fireNativeTrackers(message, bidResponse) { + const nativeResponse = bidResponse.native.ortb || legacyPropertiesToOrtbNative(bidResponse.native); + if (message.action === 'click') { - trackers = adObject['native'] && adObject['native'].clickTrackers; + fireClickTrackers(nativeResponse, message?.assetId); } else { - trackers = adObject['native'] && adObject['native'].impressionTrackers; + fireImpressionTrackers(nativeResponse); + } + return message.action; +} - if (adObject['native'] && adObject['native'].javascriptTrackers) { - insertHtmlIntoIframe(adObject['native'].javascriptTrackers); +export function fireImpressionTrackers(nativeResponse, {runMarkup = (mkup) => insertHtmlIntoIframe(mkup), fetchURL = triggerPixel} = {}) { + const impTrackers = (nativeResponse.eventtrackers || []) + .filter(tracker => tracker.event === TRACKER_EVENTS.impression); + + let {img, js} = impTrackers.reduce((tally, tracker) => { + if (TRACKER_METHODS.hasOwnProperty(tracker.method)) { + tally[TRACKER_METHODS[tracker.method]].push(tracker.url) } + return tally; + }, {img: [], js: []}); + + if (nativeResponse.imptrackers) { + img = img.concat(nativeResponse.imptrackers); } + img.forEach(url => fetchURL(url)); - (trackers || []).forEach(triggerPixel); - return message.action; + js = js.map(url => ``); + if (nativeResponse.jstracker) { + // jstracker is already HTML markup + js = js.concat([nativeResponse.jstracker]); + } + if (js.length) { + runMarkup(js.join('\n')); + } +} + +export function fireClickTrackers(nativeResponse, assetId = null, {fetchURL = triggerPixel} = {}) { + // legacy click tracker + if (!assetId) { + (nativeResponse.link?.clicktrackers || []).forEach(url => fetchURL(url)); + } else { + // ortb click tracker. This will try to call the clicktracker associated with the asset; + // will fallback to the link if none is found. + const assetIdLinkMap = (nativeResponse.assets || []) + .filter(a => a.link) + .reduce((map, asset) => { + map[asset.id] = asset.link; + return map + }, {}); + const masterClickTrackers = nativeResponse.link?.clicktrackers || []; + let assetLink = assetIdLinkMap[assetId]; + let clickTrackers = masterClickTrackers; + if (assetLink) { + clickTrackers = assetLink.clicktrackers || []; + } + clickTrackers.forEach(url => fetchURL(url)); + } } /** @@ -183,7 +370,7 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) { value = placeholder; } - let assetSendTargetingKeys = deepAccess(adUnit, `nativeParams.${asset}.sendTargetingKeys`) + let assetSendTargetingKeys = deepAccess(adUnit, `nativeParams.${asset}.sendTargetingKeys`); if (typeof assetSendTargetingKeys !== 'boolean') { assetSendTargetingKeys = deepAccess(adUnit, `nativeParams.ext.${asset}.sendTargetingKeys`); } @@ -198,62 +385,65 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) { return keyValues; } -/** - * Constructs a message object containing asset values for each of the - * requested data keys. - */ -export function getAssetMessage(data, adObject) { +const getNativeRequest = (bidResponse) => auctionManager.index.getAdUnit(bidResponse)?.nativeOrtbRequest; + +function assetsMessage(data, adObject, keys, {getNativeReq = getNativeRequest} = {}) { const message = { message: 'assetResponse', adId: data.adId, - assets: [], }; - if (adObject.native.hasOwnProperty('adTemplate')) { - message.adTemplate = getAssetValue(adObject.native['adTemplate']); - } if (adObject.native.hasOwnProperty('rendererUrl')) { - message.rendererUrl = getAssetValue(adObject.native['rendererUrl']); + // Pass to Prebid Universal Creative all assets, the legacy ones + the ortb ones (under ortb property) + const ortbRequest = getNativeReq(adObject); + let nativeResp = adObject.native; + const ortbResponse = adObject.native?.ortb; + let legacyResponse = {}; + if (ortbRequest && ortbResponse) { + legacyResponse = toLegacyResponse(ortbResponse, ortbRequest); + nativeResp = { + ...adObject.native, + ...legacyResponse + }; } + if (adObject.native.ortb) { + message.ortb = adObject.native.ortb; + } + message.assets = []; - data.assets.forEach(asset => { - const key = getKeyByValue(CONSTANTS.NATIVE_KEYS, asset); - const value = getAssetValue(adObject.native[key]); - - message.assets.push({ key, value }); - }); - - return message; -} - -export function getAllAssetsMessage(data, adObject) { - const message = { - message: 'assetResponse', - adId: data.adId, - assets: [] - }; - - Object.keys(adObject.native).forEach(function(key, index) { - if (key === 'adTemplate' && adObject.native[key]) { - message.adTemplate = getAssetValue(adObject.native[key]); - } else if (key === 'rendererUrl' && adObject.native[key]) { - message.rendererUrl = getAssetValue(adObject.native[key]); + (keys == null ? Object.keys(nativeResp) : keys).forEach(function(key) { + if (key === 'adTemplate' && nativeResp[key]) { + message.adTemplate = getAssetValue(nativeResp[key]); + } else if (key === 'rendererUrl' && nativeResp[key]) { + message.rendererUrl = getAssetValue(nativeResp[key]); } else if (key === 'ext') { - Object.keys(adObject.native[key]).forEach(extKey => { - if (adObject.native[key][extKey]) { - const value = getAssetValue(adObject.native[key][extKey]); + Object.keys(nativeResp[key]).forEach(extKey => { + if (nativeResp[key][extKey]) { + const value = getAssetValue(nativeResp[key][extKey]); message.assets.push({ key: extKey, value }); } }) - } else if (adObject.native[key] && CONSTANTS.NATIVE_KEYS.hasOwnProperty(key)) { - const value = getAssetValue(adObject.native[key]); + } else if (nativeResp[key] && CONSTANTS.NATIVE_KEYS.hasOwnProperty(key)) { + const value = getAssetValue(nativeResp[key]); message.assets.push({ key, value }); } }); - return message; } +/** + * Constructs a message object containing asset values for each of the + * requested data keys. + */ +export function getAssetMessage(data, adObject, {getNativeReq = getNativeRequest} = {}) { + const keys = data.assets.map((k) => getKeyByValue(CONSTANTS.NATIVE_KEYS, k)); + return assetsMessage(data, adObject, keys, {getNativeReq}); +} + +export function getAllAssetsMessage(data, adObject, {getNativeReq = getNativeRequest} = {}) { + return assetsMessage(data, adObject, null, {getNativeReq}); +} + /** * Native assets can be a string or an object with a url prop. Returns the value * appropriate for sending in adserver targeting or placeholder replacement. @@ -280,3 +470,314 @@ function getNativeKeys(adUnit) { ...extraNativeKeys } } + +/** + * converts Prebid legacy native assets request to OpenRTB format + * @param {object} legacyNativeAssets an object that describes a native bid request in Prebid proprietary format + * @returns an OpenRTB format of the same bid request + */ +export function toOrtbNativeRequest(legacyNativeAssets) { + if (!legacyNativeAssets && !isPlainObject(legacyNativeAssets)) { + logError('Native assets object is empty or not an object: ', legacyNativeAssets); + return; + } + const ortb = { + ver: '1.2', + assets: [] + }; + for (let key in legacyNativeAssets) { + // skip conversion for non-asset keys + if (NATIVE_KEYS_THAT_ARE_NOT_ASSETS.includes(key)) continue; + if (!NATIVE_KEYS.hasOwnProperty(key)) { + logError(`Unrecognized native asset code: ${key}. Asset will be ignored.`); + continue; + } + + const asset = legacyNativeAssets[key]; + let required = 0; + if (asset.required && isBoolean(asset.required)) { + required = Number(asset.required); + } + const ortbAsset = { + id: ortb.assets.length, + required + }; + // data cases + if (key in PREBID_NATIVE_DATA_KEYS_TO_ORTB) { + ortbAsset.data = { + type: NATIVE_ASSET_TYPES[PREBID_NATIVE_DATA_KEYS_TO_ORTB[key]] + } + if (asset.len) { + ortbAsset.data.len = asset.len; + } + // icon or image case + } else if (key === 'icon' || key === 'image') { + ortbAsset.img = { + type: key === 'icon' ? NATIVE_IMAGE_TYPES.ICON : NATIVE_IMAGE_TYPES.MAIN, + } + // if min_width and min_height are defined in aspect_ratio, they are preferred + if (asset.aspect_ratios) { + if (!isArray(asset.aspect_ratios)) { + logError("image.aspect_ratios was passed, but it's not a an array:", asset.aspect_ratios); + } else if (!asset.aspect_ratios.length) { + logError("image.aspect_ratios was passed, but it's empty:", asset.aspect_ratios); + } else { + const { min_width: minWidth, min_height: minHeight } = asset.aspect_ratios[0]; + if (!isInteger(minWidth) || !isInteger(minHeight)) { + logError('image.aspect_ratios min_width or min_height are invalid: ', minWidth, minHeight); + } else { + ortbAsset.img.wmin = minWidth; + ortbAsset.img.hmin = minHeight; + } + const aspectRatios = asset.aspect_ratios + .filter((ar) => ar.ratio_width && ar.ratio_height) + .map(ratio => `${ratio.ratio_width}:${ratio.ratio_height}`); + if (aspectRatios.length > 0) { + ortbAsset.img.ext = { + aspectratios: aspectRatios + } + } + } + } + + // if asset.sizes exist, by OpenRTB spec we should remove wmin and hmin + if (asset.sizes) { + if (asset.sizes.length !== 2 || !isInteger(asset.sizes[0]) || !isInteger(asset.sizes[1])) { + logError('image.sizes was passed, but its value is not an array of integers:', asset.sizes); + } else { + ortbAsset.img.w = asset.sizes[0]; + ortbAsset.img.h = asset.sizes[1]; + delete ortbAsset.img.hmin; + delete ortbAsset.img.wmin; + } + } + // title case + } else if (key === 'title') { + ortbAsset.title = { + // in openRTB, len is required for titles, while in legacy prebid was not. + // for this reason, if len is missing in legacy prebid, we're adding a default value of 140. + len: asset.len || 140 + } + // all extensions to the native bid request are passed as is + } else if (key === 'ext') { + ortbAsset.ext = asset; + // in `ext` case, required field is not needed + delete ortbAsset.required; + } + ortb.assets.push(ortbAsset); + } + return ortb; +} + +/** + * This function converts an OpenRTB native request object to Prebid proprietary + * format. The purpose of this function is to help adapters to handle the + * transition phase where publishers may be using OpenRTB objects but the + * bidder does not yet support it. + * @param {object} openRTBRequest an OpenRTB v1.2 request object + * @returns a Prebid legacy native format request + */ +export function fromOrtbNativeRequest(openRTBRequest) { + if (!isOpenRTBBidRequestValid(openRTBRequest)) { + return; + } + + const oldNativeObject = {}; + for (const asset of openRTBRequest.assets) { + if (asset.title) { + const title = { + required: asset.required ? Boolean(asset.required) : false, + len: asset.title.len + } + oldNativeObject.title = title; + } else if (asset.img) { + const image = { + required: asset.required ? Boolean(asset.required) : false, + } + if (asset.img.w && asset.img.h) { + image.sizes = [asset.img.w, asset.img.h]; + } else if (asset.img.wmin && asset.img.hmin) { + image.aspect_ratios = { + min_width: asset.img.wmin, + min_height: asset.img.hmin, + ratio_width: asset.img.wmin, + ratio_height: asset.img.hmin + } + } + + if (asset.img.type === NATIVE_IMAGE_TYPES.MAIN) { + oldNativeObject.image = image; + } else { + oldNativeObject.icon = image; + } + } else if (asset.data) { + let assetType = Object.keys(NATIVE_ASSET_TYPES).find(k => NATIVE_ASSET_TYPES[k] === asset.data.type); + let prebidAssetName = Object.keys(PREBID_NATIVE_DATA_KEYS_TO_ORTB).find(k => PREBID_NATIVE_DATA_KEYS_TO_ORTB[k] === assetType); + oldNativeObject[prebidAssetName] = { + required: asset.required ? Boolean(asset.required) : false, + } + if (asset.data.len) { + oldNativeObject[prebidAssetName].len = asset.data.len; + } + } + // video was not supported by old prebid assets + } + return oldNativeObject; +} + +/** + * Converts an OpenRTB request to a proprietary Prebid.js format. + * The proprietary Prebid format has many limitations and will be dropped in + * the future; adapters are encouraged to stop using it in favour of OpenRTB format. + * IMPLEMENTATION DETAILS: This function returns the same exact object if no + * conversion is needed. If a conversion is needed (meaning, at least one + * bidRequest contains a native.ortb definition), it will return a copy. + * + * @param {BidRequest[]} bidRequests an array of valid bid requests + * @returns an array of valid bid requests where the openRTB bids are converted to proprietary format. + */ +export function convertOrtbRequestToProprietaryNative(bidRequests) { + if (FEATURES.NATIVE) { + if (!bidRequests || !isArray(bidRequests)) return bidRequests; + // check if a conversion is needed + if (!bidRequests.some(bidRequest => (bidRequest?.mediaTypes || {})[NATIVE]?.ortb)) { + return bidRequests; + } + let bidRequestsCopy = deepClone(bidRequests); + // convert Native ORTB definition to old-style prebid native definition + for (const bidRequest of bidRequestsCopy) { + if (bidRequest.mediaTypes && bidRequest.mediaTypes[NATIVE] && bidRequest.mediaTypes[NATIVE].ortb) { + bidRequest.mediaTypes[NATIVE] = Object.assign( + pick(bidRequest.mediaTypes[NATIVE], NATIVE_KEYS_THAT_ARE_NOT_ASSETS), + fromOrtbNativeRequest(bidRequest.mediaTypes[NATIVE].ortb) + ); + bidRequest.nativeParams = processNativeAdUnitParams(bidRequest.mediaTypes[NATIVE]); + } + } + return bidRequestsCopy; + } + return bidRequests; +} + +/** + * convert PBJS proprietary native properties that are *not* assets to the ORTB native format. + * + * @param legacyNative `bidResponse.native` object as returned by adapters + */ +export function legacyPropertiesToOrtbNative(legacyNative) { + const response = { + link: {}, + eventtrackers: [] + } + Object.entries(legacyNative).forEach(([key, value]) => { + switch (key) { + case 'clickUrl': + response.link.url = value; + break; + case 'clickTrackers': + response.link.clicktrackers = Array.isArray(value) ? value : [value]; + break; + case 'impressionTrackers': + (Array.isArray(value) ? value : [value]).forEach(url => { + response.eventtrackers.push({ + event: TRACKER_EVENTS.impression, + method: TRACKER_METHODS.img, + url + }); + }); + break; + case 'javascriptTrackers': + // jstracker is deprecated, but we need to use it here since 'javascriptTrackers' is markup, not an url + // TODO: at the time of writing this, core expected javascriptTrackers to be a string (despite the name), + // but many adapters are passing an array. It's possible that some of them are, in fact, passing URLs and not markup + // in general, native trackers seem to be neglected and/or broken + response.jstracker = Array.isArray(value) ? value.join('') : value; + break; + } + }) + return response; +} + +export function toOrtbNativeResponse(legacyResponse, ortbRequest) { + const ortbResponse = { + ...legacyPropertiesToOrtbNative(legacyResponse), + assets: [] + }; + + function useRequestAsset(predicate, fn) { + let asset = ortbRequest.assets.find(predicate); + if (asset != null) { + asset = deepClone(asset); + fn(asset); + ortbResponse.assets.push(asset); + } + } + + Object.keys(legacyResponse).filter(key => !!legacyResponse[key]).forEach(key => { + const value = legacyResponse[key]; + switch (key) { + // process titles + case 'title': + useRequestAsset(asset => asset.title != null, titleAsset => { + titleAsset.title = { + text: value + }; + }) + break; + case 'image': + case 'icon': + const imageType = key === 'image' ? NATIVE_IMAGE_TYPES.MAIN : NATIVE_IMAGE_TYPES.ICON; + useRequestAsset(asset => asset.img != null && asset.img.type === imageType, imageAsset => { + imageAsset.img = { + url: value + }; + }) + break; + default: + if (key in PREBID_NATIVE_DATA_KEYS_TO_ORTB) { + useRequestAsset(asset => asset.data != null && asset.data.type === NATIVE_ASSET_TYPES[PREBID_NATIVE_DATA_KEYS_TO_ORTB[key]], dataAsset => { + dataAsset.data = { + value + }; + }) + } + break; + } + }); + return ortbResponse; +} + +/** + * Generates a legacy response from an ortb response. Useful during the transition period. + * @param {*} ortbResponse a standard ortb response object + * @param {*} ortbRequest the ortb request, useful to match ids. + * @returns an object containing the response in legacy native format: { title: "this is a title", image: ... } + */ +function toLegacyResponse(ortbResponse, ortbRequest) { + const legacyResponse = {}; + const requestAssets = ortbRequest?.assets || []; + legacyResponse.clickUrl = ortbResponse.link.url; + legacyResponse.privacyLink = ortbResponse.privacy; + for (const asset of ortbResponse?.assets || []) { + const requestAsset = requestAssets.find(reqAsset => asset.id === reqAsset.id); + if (asset.title) { + legacyResponse.title = asset.title.text; + } else if (asset.img) { + legacyResponse[requestAsset.img.type === NATIVE_IMAGE_TYPES.MAIN ? 'image' : 'icon'] = asset.img.url; + } else if (asset.data) { + legacyResponse[PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE[NATIVE_ASSET_TYPES_INVERSE[requestAsset.data.type]]] = asset.data.value; + } + } + return legacyResponse; +} + +/** + * Inverts key-values of an object. + */ +function inverse(obj) { + var retobj = {}; + for (var key in obj) { + retobj[obj[key]] = key; + } + return retobj; +} diff --git a/src/prebid.js b/src/prebid.js index 83b3b4580ef..c9e26225076 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -5,15 +5,15 @@ import { adUnitsFilter, flatten, getHighestCpm, isArrayOfNums, isGptPubadsDefined, uniques, logInfo, contains, logError, isArray, deepClone, deepAccess, isNumber, logWarn, logMessage, isFn, transformAdServerTargetingObj, bind, replaceAuctionPrice, replaceClickThrough, insertElement, - inIframe, callBurl, createInvisibleIframe, generateUUID, unsupportedBidderMessage, isEmpty + inIframe, callBurl, createInvisibleIframe, generateUUID, unsupportedBidderMessage, isEmpty, mergeDeep, deepSetValue } from './utils.js'; import { listenMessagesFromCreative } from './secureCreatives.js'; import { userSync } from './userSync.js'; import { config } from './config.js'; import { auctionManager } from './auctionManager.js'; import { filters, targeting } from './targeting.js'; -import { hook } from './hook.js'; -import { sessionLoader } from './debugging.js'; +import {hook, wrapHook} from './hook.js'; +import { loadSession } from './debugging.js'; import {includes} from './polyfill.js'; import { adunitCounter } from './adUnits.js'; import { executeRenderer, isRendererRequired } from './Renderer.js'; @@ -36,7 +36,7 @@ const eventValidators = { }; // initialize existing debugging sessions if present -sessionLoader(); +loadSession(); /* Public vars */ $$PREBID_GLOBAL$$.bidderSettings = $$PREBID_GLOBAL$$.bidderSettings || {}; @@ -129,6 +129,16 @@ function validateVideoMediaType(adUnit) { function validateNativeMediaType(adUnit) { const validatedAdUnit = deepClone(adUnit); const native = validatedAdUnit.mediaTypes.native; + // if native assets are specified in OpenRTB format, remove legacy assets and print a warn. + if (native.ortb) { + const legacyNativeKeys = Object.keys(CONSTANTS.NATIVE_KEYS).filter(key => CONSTANTS.NATIVE_KEYS[key].includes('hb_native_')); + const nativeKeys = Object.keys(native); + const intersection = nativeKeys.filter(nativeKey => legacyNativeKeys.includes(nativeKey)); + if (intersection.length > 0) { + logError(`when using native OpenRTB format, you cannot use legacy native properties. Deleting ${intersection} keys from request.`); + intersection.forEach(legacyKey => delete validatedAdUnit.mediaTypes.native[legacyKey]); + } + } if (native.image && native.image.sizes && !Array.isArray(native.image.sizes)) { logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.'); delete validatedAdUnit.mediaTypes.native.image.sizes; @@ -188,10 +198,13 @@ export const adUnitSetupChecks = { validateAdUnit, validateBannerMediaType, validateVideoMediaType, - validateNativeMediaType, validateSizes }; +if (FEATURES.NATIVE) { + Object.assign(adUnitSetupChecks, {validateNativeMediaType}); +} + export const checkAdUnitSetup = hook('sync', function (adUnits) { const validatedAdUnits = []; @@ -212,7 +225,7 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { if (mediaTypes.video.hasOwnProperty('pos')) validatedVideo = validateAdUnitPos(validatedVideo, 'video'); } - if (mediaTypes.native) { + if (FEATURES.NATIVE && mediaTypes.native) { validatedNative = validatedVideo ? validateNativeMediaType(validatedVideo) : validatedBanner ? validateNativeMediaType(validatedBanner) : validateNativeMediaType(adUnit); } @@ -570,15 +583,31 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { * @param {String} requestOptions.auctionId * @alias module:pbjs.requestBids */ -$$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId } = {}) { - events.emit(REQUEST_BIDS); - const cbTimeout = timeout || config.getConfig('bidderTimeout'); - adUnits = (adUnits && config.convertAdUnitFpd(isArray(adUnits) ? adUnits : [adUnits])) || $$PREBID_GLOBAL$$.adUnits; - - logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); +$$PREBID_GLOBAL$$.requestBids = (function() { + const delegate = hook('async', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId, ortb2 } = {}) { + events.emit(REQUEST_BIDS); + const cbTimeout = timeout || config.getConfig('bidderTimeout'); + logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + const ortb2Fragments = { + global: mergeDeep({}, config.getAnyConfig('ortb2') || {}, ortb2 || {}), + bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, cfg.ortb2]).filter(([_, ortb2]) => ortb2 != null)) + } + return startAuction({bidsBackHandler, timeout: cbTimeout, adUnits, adUnitCodes, labels, auctionId, ortb2Fragments}); + }, 'requestBids'); + + return wrapHook(delegate, function requestBids(req = {}) { + // if the request does not specify adUnits, clone the global adUnit array - before + // any hook has a chance to run. + // otherwise, if the caller goes on to use addAdUnits/removeAdUnits, any asynchronous logic + // in any hook might see their effects. + let adUnits = req.adUnits || $$PREBID_GLOBAL$$.adUnits; + req.adUnits = (isArray(adUnits) ? adUnits.slice() : [adUnits]); + return delegate.call(this, req); + }); +})(); +export const startAuction = hook('async', function ({ bidsBackHandler, timeout: cbTimeout, adUnits, adUnitCodes, labels, auctionId, ortb2Fragments } = {}) { const s2sBidders = getS2SBidderSet(config.getConfig('s2sConfig') || []); - adUnits = checkAdUnitSetup(adUnits); if (adUnitCodes && adUnitCodes.length) { @@ -607,6 +636,9 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo adUnit.transactionId = generateUUID(); + // Populate ortb2Imp.ext.tid with transactionId. Specifying a transaction ID per item in the ortb impression array, lets multiple transaction IDs be transmitted in a single bid request. + deepSetValue(adUnit, 'ortb2Imp.ext.tid', adUnit.transactionId) + bidders.forEach(bidder => { const adapter = bidderRegistry[bidder]; const spec = adapter && adapter.getSpec && adapter.getSpec(); @@ -639,7 +671,15 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo return; } - const auction = auctionManager.createAuction({ adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels, auctionId }); + const auction = auctionManager.createAuction({ + adUnits, + adUnitCodes, + callback: bidsBackHandler, + cbTimeout, + labels, + auctionId, + ortb2Fragments + }); let adUnitsLen = adUnits.length; if (adUnitsLen > 15) { @@ -648,7 +688,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo adUnitCodes.forEach(code => targeting.setLatestAuctionForAdUnit(code, auction.getAuctionId())); auction.callBids(); -}); +}, 'startAuction'); export function executeCallbacks(fn, reqBidsConfigObj) { runAll(storageCallbacks); @@ -674,7 +714,7 @@ $$PREBID_GLOBAL$$.requestBids.before(executeCallbacks, 49); */ $$PREBID_GLOBAL$$.addAdUnits = function (adUnitArr) { logInfo('Invoking $$PREBID_GLOBAL$$.addAdUnits', arguments); - $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, config.convertAdUnitFpd(isArray(adUnitArr) ? adUnitArr : [adUnitArr])); + $$PREBID_GLOBAL$$.adUnits.push.apply($$PREBID_GLOBAL$$.adUnits, isArray(adUnitArr) ? adUnitArr : [adUnitArr]); // emit event events.emit(ADD_AD_UNITS); }; @@ -913,56 +953,16 @@ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { * @param {Object} options * @alias module:pbjs.getConfig */ -$$PREBID_GLOBAL$$.getConfig = config.getConfig; -$$PREBID_GLOBAL$$.readConfig = config.readConfig; +$$PREBID_GLOBAL$$.getConfig = config.getAnyConfig; +$$PREBID_GLOBAL$$.readConfig = config.readAnyConfig; $$PREBID_GLOBAL$$.mergeConfig = config.mergeConfig; $$PREBID_GLOBAL$$.mergeBidderConfig = config.mergeBidderConfig; /** * Set Prebid config options. - * (Added in version 0.27.0). - * - * `setConfig` is designed to allow for advanced configuration while - * reducing the surface area of the public API. For more information - * about the move to `setConfig` (and the resulting deprecations of - * some other public methods), see [the Prebid 1.0 public API - * proposal](https://gist.github.com/mkendall07/51ee5f6b9f2df01a89162cf6de7fe5b6). - * - * #### Troubleshooting your configuration - * - * If you call `pbjs.setConfig` without an object, e.g., - * - * `pbjs.setConfig('debug', 'true'))` - * - * then Prebid.js will print an error to the console that says: - * - * ``` - * ERROR: setConfig options must be an object - * ``` - * - * If you don't see that message, you can assume the config object is valid. + * See https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html * * @param {Object} options Global Prebid configuration object. Must be JSON - no JavaScript functions are allowed. - * @param {string} options.bidderSequence The order in which bidders are called. Example: `pbjs.setConfig({ bidderSequence: "fixed" })`. Allowed values: `"fixed"` (order defined in `adUnit.bids` array on page), `"random"`. - * @param {boolean} options.debug Turn debug logging on/off. Example: `pbjs.setConfig({ debug: true })`. - * @param {string} options.priceGranularity The bid price granularity to use. Example: `pbjs.setConfig({ priceGranularity: "medium" })`. Allowed values: `"low"` ($0.50), `"medium"` ($0.10), `"high"` ($0.01), `"auto"` (sliding scale), `"dense"` (like `"auto"`, with smaller increments at lower CPMs), or a custom price bucket object, e.g., `{ "buckets" : [{"min" : 0,"max" : 20,"increment" : 0.1,"cap" : true}]}`. - * @param {boolean} options.enableSendAllBids Turn "send all bids" mode on/off. Example: `pbjs.setConfig({ enableSendAllBids: true })`. - * @param {number} options.bidderTimeout Set a global bidder timeout, in milliseconds. Example: `pbjs.setConfig({ bidderTimeout: 3000 })`. Note that it's still possible for a bid to get into the auction that responds after this timeout. This is due to how [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) works in JS: it queues the callback in the event loop in an approximate location that should execute after this time but it is not guaranteed. For more information about the asynchronous event loop and `setTimeout`, see [How JavaScript Timers Work](https://johnresig.com/blog/how-javascript-timers-work/). - * @param {string} options.publisherDomain The publisher's domain where Prebid is running, for cross-domain iFrame communication. Example: `pbjs.setConfig({ publisherDomain: "https://www.theverge.com" })`. - * @param {Object} options.s2sConfig The configuration object for [server-to-server header bidding](http://prebid.org/dev-docs/get-started-with-prebid-server.html). Example: - * @alias module:pbjs.setConfig - * ``` - * pbjs.setConfig({ - * s2sConfig: { - * accountId: '1', - * enabled: true, - * bidders: ['appnexus', 'pubmatic'], - * timeout: 1000, - * adapter: 'prebidServer', - * endpoint: 'https://prebid.adnxs.com/pbs/v1/auction' - * } - * }) - * ``` */ $$PREBID_GLOBAL$$.setConfig = config.setConfig; $$PREBID_GLOBAL$$.setBidderConfig = config.setBidderConfig; diff --git a/src/refererDetection.js b/src/refererDetection.js index 7e9f2a7e6c7..15c080f5c69 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -9,7 +9,47 @@ */ import { config } from './config.js'; -import { logWarn } from './utils.js'; +import {logWarn} from './utils.js'; + +/** + * Prepend a URL with the page's protocol (http/https), if necessary. + */ +export function ensureProtocol(url, win = window) { + if (!url) return url; + if (/\w+:\/\//.exec(url)) { + // url already has protocol + return url; + } + let windowProto = win.location.protocol; + try { + windowProto = win.top.location.protocol; + } catch (e) {} + if (/^\/\//.exec(url)) { + // url uses relative protocol ("//example.com") + return windowProto + url; + } else { + return `${windowProto}//${url}`; + } +} + +/** + * Extract the domain portion from a URL. + * @param url + * @param noLeadingWww: if true, remove 'www.' appearing at the beginning of the domain. + * @param noPort: if true, do not include the ':[port]' portion + */ +export function parseDomain(url, {noLeadingWww = false, noPort = false} = {}) { + try { + url = new URL(ensureProtocol(url)); + } catch (e) { + return; + } + url = noPort ? url.hostname : url.host; + if (noLeadingWww && url.startsWith('www.')) { + url = url.substring(4); + } + return url; +} /** * @param {Window} win Window @@ -42,10 +82,6 @@ export function detectReferer(win) { * @returns {string|null} */ function getCanonicalUrl(doc) { - let pageURL = config.getConfig('pageUrl'); - - if (pageURL) return pageURL; - try { const element = doc.querySelector("link[rel='canonical']"); @@ -59,14 +95,20 @@ export function detectReferer(win) { return null; } + // TODO: the meaning of "reachedTop" seems to be intentionally ambiguous - best to leave them out of + // the typedef for now. (for example, unit tests enforce that "reachedTop" should be false in some situations where we + // happily provide a location for the top). + /** - * Referer info * @typedef {Object} refererInfo - * @property {string} referer detected top url - * @property {boolean} reachedTop whether prebid was able to walk upto top window or not - * @property {number} numIframes number of iframes - * @property {string} stack comma separated urls of all origins - * @property {string} canonicalUrl canonical URL refers to an HTML link element, with the attribute of rel="canonical", found in the element of your webpage + * @property {string|null} location the browser's location, or null if not available (due to cross-origin restrictions) + * @property {string|null} canonicalUrl the site's canonical URL as set by the publisher, through setConfig({pageUrl}) or + * @property {string|null} page the best candidate for the current page URL: `canonicalUrl`, falling back to `location` + * @property {string|null} domain the domain portion of `page` + * @property {string|null} ref the referrer (document.referrer) to the current page, or null if not available (due to cross-origin restrictions) + * @property {string} topmostLocation of the top-most frame for which we could guess the location. Outside of cross-origin scenarios, this is equivalent to `location`. + * @property {number} numIframes number of steps between window.self and window.top + * @property {Array[string|null]} stack our best guess at the location for each frame, in the direction top -> self. */ /** @@ -79,19 +121,20 @@ export function detectReferer(win) { const ancestors = getAncestorOrigins(win); const maxNestedIframes = config.getConfig('maxNestedIframes'); let currentWindow; - let bestReferrer; + let bestLocation; let bestCanonicalUrl; let reachedTop = false; let level = 0; let valuesFromAmp = false; let inAmpFrame = false; + let hasTopLocation = false; do { const previousWindow = currentWindow; const wasInAmpFrame = inAmpFrame; let currentLocation; let crossOrigin = false; - let foundReferrer = null; + let foundLocation = null; inAmpFrame = false; currentWindow = currentWindow ? currentWindow.parent : win; @@ -107,8 +150,9 @@ export function detectReferer(win) { const context = previousWindow.context; try { - foundReferrer = context.sourceUrl; - bestReferrer = foundReferrer; + foundLocation = context.sourceUrl; + bestLocation = foundLocation; + hasTopLocation = true; valuesFromAmp = true; @@ -124,10 +168,11 @@ export function detectReferer(win) { logWarn('Trying to access cross domain iframe. Continuing without referrer and location'); try { + // the referrer to an iframe is the parent window const referrer = previousWindow.document.referrer; if (referrer) { - foundReferrer = referrer; + foundLocation = referrer; if (currentWindow === win.top) { reachedTop = true; @@ -135,18 +180,21 @@ export function detectReferer(win) { } } catch (e) { /* Do nothing */ } - if (!foundReferrer && ancestors && ancestors[level - 1]) { - foundReferrer = ancestors[level - 1]; + if (!foundLocation && ancestors && ancestors[level - 1]) { + foundLocation = ancestors[level - 1]; + if (currentWindow === win.top) { + hasTopLocation = true; + } } - if (foundReferrer && !valuesFromAmp) { - bestReferrer = foundReferrer; + if (foundLocation && !valuesFromAmp) { + bestLocation = foundLocation; } } } else { if (currentLocation) { - foundReferrer = currentLocation; - bestReferrer = foundReferrer; + foundLocation = currentLocation; + bestLocation = foundLocation; valuesFromAmp = false; if (currentWindow === win.top) { @@ -165,23 +213,49 @@ export function detectReferer(win) { } } - stack.push(foundReferrer); + stack.push(foundLocation); level++; } while (currentWindow !== win.top && level < maxNestedIframes); stack.reverse(); + let ref; + try { + ref = win.top.document.referrer; + } catch (e) {} + + const location = reachedTop || hasTopLocation ? bestLocation : null; + const canonicalUrl = config.getConfig('pageUrl') || bestCanonicalUrl || null; + const page = ensureProtocol(canonicalUrl, win) || location; + return { - referer: bestReferrer || null, reachedTop, isAmp: valuesFromAmp, numIframes: level - 1, stack, - canonicalUrl: bestCanonicalUrl || null + topmostLocation: bestLocation || null, + location, + canonicalUrl, + page, + domain: parseDomain(page) || null, + ref: ref || null, + // TODO: the "legacy" refererInfo object is provided here, for now, to accomodate + // adapters that decided to just send it verbatim to their backend. + legacy: { + reachedTop, + isAmp: valuesFromAmp, + numIframes: level - 1, + stack, + referer: bestLocation || null, + canonicalUrl + } }; } return refererInfo; } +/** + * @type {function(): refererInfo} + */ export const getRefererInfo = detectReferer(window); diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 5cfa25fbbc8..d0c06f6ab1a 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -15,13 +15,19 @@ import {emitAdRenderFail, emitAdRenderSucceeded} from './adRendering.js'; const BID_WON = constants.EVENTS.BID_WON; const STALE_RENDER = constants.EVENTS.STALE_RENDER; +const WON_AD_IDS = new WeakSet(); const HANDLER_MAP = { 'Prebid Request': handleRenderRequest, - 'Prebid Native': handleNativeRequest, 'Prebid Event': handleEventRequest, } +if (FEATURES.NATIVE) { + Object.assign(HANDLER_MAP, { + 'Prebid Native': handleNativeRequest, + }) +} + export function listenMessagesFromCreative() { window.addEventListener('message', receiveMessage, false); } @@ -108,6 +114,13 @@ function handleNativeRequest(reply, data, adObject) { logError(`Cannot find ad '${data.adId}' for x-origin event request`); return; } + + if (!WON_AD_IDS.has(adObject)) { + WON_AD_IDS.add(adObject); + auctionManager.addWinningBid(adObject); + events.emit(BID_WON, adObject); + } + switch (data.action) { case 'assetRequest': reply(getAssetMessage(data, adObject)); @@ -121,12 +134,7 @@ function handleNativeRequest(reply, data, adObject) { resizeRemoteCreative(adObject); break; default: - const trackerType = fireNativeTrackers(data, adObject); - if (trackerType === 'click') { - return; - } - auctionManager.addWinningBid(adObject); - events.emit(BID_WON, adObject); + fireNativeTrackers(data, adObject); } } @@ -185,7 +193,7 @@ function resizeRemoteCreative({ adId, adUnitCode, width, height }) { let element = getElementByAdUnit(elmType + ':not([style*="display: none"])'); if (element) { let elementStyle = element.style; - elementStyle.width = width + 'px'; + elementStyle.width = width ? width + 'px' : '100%'; elementStyle.height = height + 'px'; } else { logWarn(`Unable to locate matching page element for adUnitCode ${adUnitCode}. Can't resize it to ad's dimensions. Please review setup.`); diff --git a/src/sizeMapping.js b/src/sizeMapping.js index 4333608ca95..00a24bbdb5d 100644 --- a/src/sizeMapping.js +++ b/src/sizeMapping.js @@ -1,6 +1,7 @@ import { config } from './config.js'; import {logWarn, logInfo, isPlainObject, deepAccess, deepClone, getWindowTop} from './utils.js'; import {includes} from './polyfill.js'; +import {BANNER} from './mediaTypes.js'; let sizeConfig = []; @@ -75,22 +76,19 @@ export function resolveStatus({labels = [], labelAll = false, activeLabels = []} } else { mediaTypes = {}; } - } else { - mediaTypes = deepClone(mediaTypes); } let oldSizes = deepAccess(mediaTypes, 'banner.sizes'); if (maps.shouldFilter && oldSizes) { + mediaTypes = deepClone(mediaTypes); mediaTypes.banner.sizes = oldSizes.filter(size => maps.sizesSupported[size]); } - let allMediaTypes = Object.keys(mediaTypes); - let results = { active: ( - allMediaTypes.every(type => type !== 'banner') + !mediaTypes.hasOwnProperty(BANNER) ) || ( - allMediaTypes.some(type => type === 'banner') && deepAccess(mediaTypes, 'banner.sizes.length') > 0 && ( + deepAccess(mediaTypes, 'banner.sizes.length') > 0 && ( labels.length === 0 || ( (!labelAll && ( labels.some(label => maps.labels[label]) || @@ -111,7 +109,7 @@ export function resolveStatus({labels = [], labelAll = false, activeLabels = []} results.filterResults = { before: oldSizes, after: mediaTypes.banner.sizes - } + }; } return results; diff --git a/src/storageManager.js b/src/storageManager.js index f13a462eae7..4ab224f8d9b 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -1,6 +1,5 @@ import {hook} from './hook.js'; import {hasDeviceAccess, checkCookieSupport, logError, logInfo, isPlainObject} from './utils.js'; -import {includes} from './polyfill.js'; import {bidderSettings as defaultBidderSettings} from './bidderSettings.js'; const moduleTypeWhiteList = ['core', 'prebid-module']; @@ -26,20 +25,18 @@ export let storageCallbacks = []; * @param {storageOptions} options */ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = {}, {bidderSettings = defaultBidderSettings} = {}) { - function isBidderDisallowed() { + function isBidderAllowed() { if (bidderCode == null) { - return false; + return true; } const storageAllowed = bidderSettings.get(bidderCode, 'storageAllowed'); - return storageAllowed == null ? false : !storageAllowed; + return storageAllowed == null ? false : storageAllowed; } + + const isVendorless = moduleTypeWhiteList.includes(moduleType); + function isValid(cb) { - if (includes(moduleTypeWhiteList, moduleType)) { - let result = { - valid: true - } - return cb(result); - } else if (isBidderDisallowed()) { + if (!isBidderAllowed()) { logInfo(`bidderSettings denied access to device storage for bidder '${bidderCode}'`); const result = {valid: false}; return cb(result); @@ -48,7 +45,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = let hookDetails = { hasEnforcementHook: false } - validateStorageEnforcement(gvlid, bidderCode || moduleName, moduleType, hookDetails, function(result) { + validateStorageEnforcement(isVendorless, gvlid, bidderCode || moduleName, hookDetails, function(result) { if (result && result.hasEnforcementHook) { value = cb(result); } else { @@ -149,11 +146,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = const cookiesAreEnabled = function (done) { let cb = function (result) { if (result && result.valid) { - if (checkCookieSupport()) { - return true; - } - window.document.cookie = 'prebid.cookieTest'; - return window.document.cookie.indexOf('prebid.cookieTest') !== -1; + return checkCookieSupport(); } return false; } @@ -303,7 +296,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = /** * This hook validates the storage enforcement if gdprEnforcement module is included */ -export const validateStorageEnforcement = hook('async', function(gvlid, moduleName, moduleType, hookDetails, callback) { +export const validateStorageEnforcement = hook('async', function(isVendorless, gvlid, moduleName, hookDetails, callback) { callback(hookDetails); }, 'validateStorageEnforcement'); @@ -322,13 +315,12 @@ export function getCoreStorageManager(moduleName) { * @param {Number=} gvlid? Vendor id - required for proper GDPR integration * @param {string=} bidderCode? - required for bid adapters * @param {string=} moduleName? module name - * @param {string=} moduleType? module type */ -export function getStorageManager({gvlid, moduleName, bidderCode, moduleType} = {}) { +export function getStorageManager({gvlid, moduleName, bidderCode} = {}) { if (arguments.length > 1 || (arguments.length > 0 && !isPlainObject(arguments[0]))) { throw new Error('Invalid invocation for getStorageManager') } - return newStorageManager({gvlid, moduleName, bidderCode, moduleType}); + return newStorageManager({gvlid, moduleName, bidderCode}); } export function resetData() { diff --git a/src/targeting.js b/src/targeting.js index 8584927c468..d2b3e9f8470 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -63,7 +63,7 @@ export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, } return bidsReceived; -}) +}); /** * A descending sort function that will sort the list of objects based on the following two dimensions: @@ -176,7 +176,7 @@ export function newTargeting(auctionManager) { */ function getDealBids(adUnitCodes, bidsReceived) { if (config.getConfig('targetingControls.alwaysIncludeDeals') === true) { - const standardKeys = TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS); + const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS.slice(); // we only want the top bid from bidders who have multiple entries per ad unit code const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm); @@ -583,7 +583,10 @@ export function newTargeting(auctionManager) { } function getCustomKeys() { - let standardKeys = getStandardKeys().concat(NATIVE_TARGETING_KEYS); + let standardKeys = getStandardKeys(); + if (FEATURES.NATIVE) { + standardKeys = standardKeys.concat(NATIVE_TARGETING_KEYS); + } return function(key) { return standardKeys.indexOf(key) === -1; } @@ -623,7 +626,7 @@ export function newTargeting(auctionManager) { * @return {targetingArray} all non-winning bids targeting */ function getBidLandscapeTargeting(adUnitCodes, bidsReceived) { - const standardKeys = TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS); + const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS.slice(); const adUnitBidLimit = config.getConfig('sendBidsControl.bidLimit'); const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, adUnitBidLimit); const allowSendAllBidsTargetingKeys = config.getConfig('targetingControls.allowSendAllBidsTargetingKeys'); diff --git a/src/userSync.js b/src/userSync.js index 96c3d662cad..dec8f650930 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -311,16 +311,20 @@ export function newUserSync(userSyncDependencies) { } } return true; - } + }; return publicApi; } -const browserSupportsCookies = !isSafariBrowser() && storage.cookiesAreEnabled(); - -export const userSync = newUserSync({ +export const userSync = newUserSync(Object.defineProperties({ config: config.getConfig('userSync'), - browserSupportsCookies: browserSupportsCookies -}); +}, { + browserSupportsCookies: { + get: function() { + // call storage lazily to give time for consent data to be available + return !isSafariBrowser() && storage.cookiesAreEnabled(); + } + } +})); /** * @typedef {Object} UserSyncDependencies diff --git a/src/utils.js b/src/utils.js index 3e96e427e2c..9e49e1687ae 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,8 +2,9 @@ import { config } from './config.js'; import clone from 'just-clone'; import {find, includes} from './polyfill.js'; import CONSTANTS from './constants.json'; +import {GreedyPromise} from './utils/promise.js'; export { default as deepAccess } from 'dlv/index.js'; -export { default as deepSetValue } from 'dset'; +export { dset as deepSetValue } from 'dset'; var tArr = 'Array'; var tStr = 'String'; @@ -51,7 +52,7 @@ export const internal = { deepEqual }; -let prebidInternal = {} +let prebidInternal = {}; /** * Returns object that is used as internal prebid namespace */ @@ -487,7 +488,7 @@ export function hasOwn(objectToCheck, propertyToCheckFor) { * @param {HTMLElement} [doc] * @param {HTMLElement} [target] * @param {Boolean} [asLastChildChild] -* @return {HTMLElement} +* @return {HTML Element} */ export function insertElement(elm, doc, target, asLastChildChild) { doc = doc || document; @@ -517,7 +518,7 @@ export function insertElement(elm, doc, target, asLastChildChild) { */ export function waitForElementToLoad(element, timeout) { let timer = null; - return new Promise((resolve) => { + return new GreedyPromise((resolve) => { const onLoad = function() { element.removeEventListener('load', onLoad); element.removeEventListener('error', onLoad); @@ -925,17 +926,6 @@ export function getUserConfiguredParams(adUnits, adUnitCode, bidder) { .filter((bidderData) => bidderData.bidder === bidder) .map((bidderData) => bidderData.params || {}); } -/** - * Returns the origin - */ -export function getOrigin() { - // IE10 does not have this property. https://gist.github.com/hbogs/7908703 - if (!window.location.origin) { - return window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''); - } else { - return window.location.origin; - } -} /** * Returns Do Not Track state @@ -965,14 +955,22 @@ export function isSlotMatchingAdUnitCode(adUnitCode) { } /** - * @summary Uses the adUnit's code in order to find a matching gptSlot on the page + * @summary Uses the adUnit's code in order to find a matching gpt slot object on the page */ -export function getGptSlotInfoForAdUnitCode(adUnitCode) { +export function getGptSlotForAdUnitCode(adUnitCode) { let matchingSlot; if (isGptPubadsDefined()) { // find the first matching gpt slot on the page matchingSlot = find(window.googletag.pubads().getSlots(), isSlotMatchingAdUnitCode(adUnitCode)); } + return matchingSlot; +}; + +/** + * @summary Uses the adUnit's code in order to find a matching gptSlot on the page + */ +export function getGptSlotInfoForAdUnitCode(adUnitCode) { + const matchingSlot = getGptSlotForAdUnitCode(adUnitCode); if (matchingSlot) { return { gptSlot: matchingSlot.getAdUnitPath(), @@ -1378,3 +1376,43 @@ export function safeJSONParse(data) { return JSON.parse(data); } catch (e) {} } + +/** + * Sets dataset attributes on a script + * @param {Script} script + * @param {object} attributes + */ +export function setScriptAttributes(script, attributes) { + for (let key in attributes) { + if (attributes.hasOwnProperty(key)) { + script.setAttribute(key, attributes[key]); + } + } +} + +/** + * Encode a string for inclusion in HTML. + * See https://pragmaticwebsecurity.com/articles/spasecurity/json-stringify-xss.html and + * https://codeql.github.com/codeql-query-help/javascript/js-bad-code-sanitization/ + * @return {string} + */ +export const escapeUnsafeChars = (() => { + const escapes = { + '<': '\\u003C', + '>': '\\u003E', + '/': '\\u002F', + '\\': '\\\\', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', + '\0': '\\0', + '\u2028': '\\u2028', + '\u2029': '\\u2029' + }; + + return function(str) { + return str.replace(/[<>\b\f\n\r\t\0\u2028\u2029\\]/g, x => escapes[x]) + } +})(); diff --git a/src/utils/gpdr.js b/src/utils/gpdr.js new file mode 100644 index 00000000000..19c7126b7d7 --- /dev/null +++ b/src/utils/gpdr.js @@ -0,0 +1,14 @@ +import {deepAccess} from '../utils.js'; + +/** + * Check if GDPR purpose 1 consent was given. + * + * @param gdprConsent GDPR consent data + * @returns {boolean} true if the gdprConsent is null-y; or GDPR does not apply; or if purpose 1 consent was given. + */ +export function hasPurpose1Consent(gdprConsent) { + if (gdprConsent?.gdprApplies) { + return deepAccess(gdprConsent, 'vendorData.purpose.consents.1') === true; + } + return true; +} diff --git a/src/utils/promise.js b/src/utils/promise.js index 8b7f9d9d40d..97c64a96f8b 100644 --- a/src/utils/promise.js +++ b/src/utils/promise.js @@ -1,36 +1,113 @@ +import {getGlobal} from '../prebidGlobal.js'; + const SUCCESS = 0; const FAIL = 1; -const RESULT = 2; /** - * @returns a {promise, resolve, reject} trio where `promise` is resolved by calling `resolve` or `reject`. + * A version of Promise that runs callbacks synchronously when it can (i.e. after it's been fulfilled or rejected). */ -export function promiseControls({promiseFactory = (resolver) => new Promise(resolver)} = {}) { - const status = {}; +export class GreedyPromise extends (getGlobal().Promise || Promise) { + #result; + #callbacks; + #parent = null; + + /** + * Convenience wrapper for setTimeout; takes care of returning an already fulfilled GreedyPromise when the delay is zero. + * + * @param {Number} delayMs delay in milliseconds + * @returns {GreedyPromise} a promise that resolves (to undefined) in `delayMs` milliseconds + */ + static timeout(delayMs = 0) { + return new GreedyPromise((resolve) => { + delayMs === 0 ? resolve() : setTimeout(resolve, delayMs); + }); + } - function finisher(slot) { - return function (val) { - if (typeof status[slot] === 'function') { - status[slot](val); - } else if (!status[slot]) { - status[slot] = true; - status[RESULT] = val; + constructor(resolver) { + const result = []; + const callbacks = []; + function handler(type, resolveFn) { + return function (value) { + if (!result.length) { + result.push(type, value); + while (callbacks.length) callbacks.shift()(); + resolveFn(value); + } } } + super( + typeof resolver !== 'function' + ? resolver // let super throw an error + : (resolve, reject) => { + const rejectHandler = handler(FAIL, reject); + const resolveHandler = (() => { + const done = handler(SUCCESS, resolve); + return value => + typeof value?.then === 'function' ? value.then(done, rejectHandler) : done(value); + })(); + try { + resolver(resolveHandler, rejectHandler); + } catch (e) { + rejectHandler(e); + } + } + ); + this.#result = result; + this.#callbacks = callbacks; + } + then(onSuccess, onError) { + if (typeof onError === 'function') { + // if an error handler is provided, attach a dummy error handler to super, + // and do the same for all promises without an error handler that precede this one in a chain. + // This is to avoid unhandled rejection events / warnings for errors that were, in fact, handled; + // since we are not using super's callback mechanisms we need to make it aware of this separately. + let node = this; + while (node) { + super.then.call(node, null, () => null); + const next = node.#parent; + node.#parent = null; // since we attached a handler already, we are no longer interested in what will happen later in the chain + node = next; + } + } + const result = this.#result; + const res = new GreedyPromise((resolve, reject) => { + const continuation = () => { + let value = result[1]; + let [handler, resolveFn] = result[0] === SUCCESS ? [onSuccess, resolve] : [onError, reject]; + if (typeof handler === 'function') { + try { + value = handler(value); + } catch (e) { + reject(e); + return; + } + resolveFn = resolve; + } + resolveFn(value); + } + result.length ? continuation() : this.#callbacks.push(continuation); + }); + res.#parent = this; + return res; + } +} + +/** + * @returns a {promise, resolve, reject} trio where `promise` is resolved by calling `resolve` or `reject`. + */ +export function defer({promiseFactory = (resolver) => new GreedyPromise(resolver)} = {}) { + function invoker(delegate) { + return (val) => delegate(val); } + let resolveFn, rejectFn; + return { promise: promiseFactory((resolve, reject) => { - if (status[SUCCESS] != null) { - resolve(status[RESULT]); - } else if (status[FAIL] != null) { - reject(status[RESULT]); - } else { - status[SUCCESS] = resolve; - status[FAIL] = reject; - } + resolveFn = resolve; + rejectFn = reject; }), - resolve: finisher(SUCCESS), - reject: finisher(FAIL) + resolve: invoker(resolveFn), + reject: invoker(rejectFn) } } diff --git a/src/video.js b/src/video.js index 977991b7134..7930e318874 100644 --- a/src/video.js +++ b/src/video.js @@ -35,15 +35,16 @@ export const hasNonVideoBidder = adUnit => export function isValidVideoBid(bid, {index = auctionManager.index} = {}) { const videoMediaType = deepAccess(index.getMediaTypes(bid), 'video'); const context = videoMediaType && deepAccess(videoMediaType, 'context'); + const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); const adUnit = index.getAdUnit(bid); // if context not defined assume default 'instream' for video bids // instream bids require a vast url or vast xml content - return checkVideoBidSetup(bid, adUnit, videoMediaType, context); + return checkVideoBidSetup(bid, adUnit, videoMediaType, context, useCacheKey); } -export const checkVideoBidSetup = hook('sync', function(bid, adUnit, videoMediaType, context) { - if (videoMediaType && context !== OUTSTREAM) { +export const checkVideoBidSetup = hook('sync', function(bid, adUnit, videoMediaType, context, useCacheKey) { + if (videoMediaType && (useCacheKey || context !== OUTSTREAM)) { // xml-only video bids require a prebid cache url if (!config.getConfig('cache.url') && bid.vastXml && !bid.vastUrl) { logError(` @@ -57,7 +58,7 @@ export const checkVideoBidSetup = hook('sync', function(bid, adUnit, videoMediaT } // outstream bids require a renderer on the bid or pub-defined on adunit - if (context === OUTSTREAM) { + if (context === OUTSTREAM && !useCacheKey) { return !!(bid.renderer || (adUnit && adUnit.renderer) || videoMediaType.renderer); } diff --git a/src/videoCache.js b/src/videoCache.js index 219bca34726..f69a20f0139 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -13,6 +13,12 @@ import { ajax } from './ajax.js'; import { config } from './config.js'; import {auctionManager} from './auctionManager.js'; +/** + * Might be useful to be configurable in the future + * Depending on publisher needs + */ +const ttlBufferInSeconds = 15; + /** * @typedef {object} CacheableUrlBid * @property {string} vastUrl A URL which loads some valid VAST XML. @@ -63,11 +69,11 @@ function wrapURI(uri, impUrl) { function toStorageRequest(bid, {index = auctionManager.index} = {}) { const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl); const auction = index.getAuction(bid); - + const ttlWithBuffer = Number(bid.ttl) + ttlBufferInSeconds; let payload = { type: 'xml', value: vastValue, - ttlseconds: Number(bid.ttl) + ttlseconds: ttlWithBuffer }; if (config.getConfig('cache.vasttrack')) { diff --git a/test/helpers/consentData.js b/test/helpers/consentData.js index 17ddc583f88..c708e397bd6 100644 --- a/test/helpers/consentData.js +++ b/test/helpers/consentData.js @@ -1,6 +1,12 @@ import {gdprDataHandler} from 'src/adapterManager.js'; +import {GreedyPromise} from '../../src/utils/promise.js'; export function mockGdprConsent(sandbox, getConsentData = () => null) { - sandbox.stub(gdprDataHandler, 'promise').get(() => Promise.resolve(getConsentData())); + sandbox.stub(gdprDataHandler, 'enabled').get(() => true) + sandbox.stub(gdprDataHandler, 'promise').get(() => GreedyPromise.resolve(getConsentData())); sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(getConsentData) } + +beforeEach(() => { + gdprDataHandler.reset(); +}) diff --git a/test/helpers/syncPromise.js b/test/helpers/syncPromise.js deleted file mode 100644 index 99361bd716e..00000000000 --- a/test/helpers/syncPromise.js +++ /dev/null @@ -1,71 +0,0 @@ -const orig = {}; -['resolve', 'reject', 'all', 'race', 'allSettled'].forEach((k) => orig[k] = Promise[k].bind(Promise)) - -// Callbacks attached through Promise.resolve(value).then(...) will usually -// not execute immediately even if `value` is immediately available. This -// breaks tests that were written before promises even though they are semantically still valid. -// They can be made to work by making promises quasi-synchronous. - -export function SyncPromise(value, fail = false) { - if (value instanceof SyncPromise) { - return value; - } else if (typeof value === 'object' && typeof value.then === 'function') { - return orig.resolve(value); - } else { - Object.assign(this, { - then: function (cb, err) { - const handler = fail ? err : cb; - if (handler != null) { - return new SyncPromise(handler(value)); - } else { - return this; - } - }, - catch: function (cb) { - if (fail) { - return new SyncPromise(cb(value)) - } else { - return this; - } - }, - finally: function (cb) { - cb(); - return this; - }, - __value: fail ? {status: 'rejected', reason: value} : {status: 'fulfilled', value} - }) - } -} - -Object.assign(SyncPromise, { - resolve: (val) => new SyncPromise(val), - reject: (val) => new SyncPromise(val, true), - race: (promises) => promises.find((p) => p instanceof SyncPromise) || orig.race(promises), - allSettled: (promises) => { - if (promises.every((p) => p instanceof SyncPromise)) { - return new SyncPromise(promises.map((p) => p.__value)) - } else { - return orig.allSettled(promises); - } - }, - all: (promises) => { - if (promises.every((p) => p instanceof SyncPromise)) { - return SyncPromise.allSettled(promises).then((result) => { - const err = result.find((r) => r.status === 'rejected'); - if (err != null) { - return new SyncPromise(err.reason, true); - } else { - return new SyncPromise(result.map((r) => r.value)) - } - }) - } else { - return orig.all(promises); - } - } -}) - -export function synchronizePromise(sandbox) { - Object.keys(orig).forEach((k) => { - sandbox.stub(window.Promise, k).callsFake(SyncPromise[k]); - }) -} diff --git a/test/mocks/analyticsStub.js b/test/mocks/analyticsStub.js index 1023db882e8..8507c5a6275 100644 --- a/test/mocks/analyticsStub.js +++ b/test/mocks/analyticsStub.js @@ -1,4 +1,4 @@ -import {_internal} from '../../src/AnalyticsAdapter.js'; +import {_internal} from '../../libraries/analyticsAdapter/AnalyticsAdapter.js'; before(() => { // stub out analytics networking to avoid random events polluting the global xhr mock diff --git a/test/pages/instream.html b/test/pages/instream.html index 887b509813a..3b5bfb3d102 100644 --- a/test/pages/instream.html +++ b/test/pages/instream.html @@ -8,8 +8,8 @@ Prebid.js video adUnit example - - + + diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index f5dac22cb58..06ab8838985 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -14,7 +14,7 @@ const AD_RENDER_SUCCEEDED = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED; const AUCTION_DEBUG = CONSTANTS.EVENTS.AUCTION_DEBUG; const ADD_AD_UNITS = CONSTANTS.EVENTS.ADD_AD_UNITS; -const AnalyticsAdapter = require('src/AnalyticsAdapter').default; +const AnalyticsAdapter = require('libraries/analyticsAdapter/AnalyticsAdapter.js').default; const config = { url: 'https://localhost:9999/endpoint', analyticsType: 'endpoint' @@ -37,6 +37,14 @@ FEATURE: Analytics Adapters API adapter.disableAnalytics(); }); + it('should track enable status in `enabled`', () => { + expect(adapter.enabled).to.equal(false); + adapter.enableAnalytics(); + expect(adapter.enabled).to.equal(true); + adapter.disableAnalytics(); + expect(adapter.enabled).to.equal(false); + }) + it(`SHOULD call the endpoint WHEN an event occurs that is to be tracked`, function () { const eventType = BID_REQUESTED; const args = { some: 'data' }; diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index baa5b4ac8c4..089aabf22a5 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -1,3 +1,5 @@ +import 'src/prebid.js'; + describe('Publisher API _ AdUnits', function () { var assert = require('chai').assert; var expect = require('chai').expect; @@ -23,10 +25,10 @@ describe('Publisher API _ AdUnits', function () { } ] }, { - fpd: { - context: { - pbAdSlot: 'adSlotTest', + ortb2Imp: { + ext: { data: { + pbadslot: 'adSlotTest', inventory: [4], keywords: 'foo,bar', visitor: [1, 2, 3], diff --git a/test/spec/adloader_spec.js b/test/spec/adloader_spec.js index 0c50e66c63c..b775ec76e9b 100644 --- a/test/spec/adloader_spec.js +++ b/test/spec/adloader_spec.js @@ -69,4 +69,27 @@ describe('adLoader', function () { expect(utilsinsertElementStub.callCount).to.equal(2); }); }); + + it('attaches passed attributes to a script', function () { + const doc = { + createElement: function () { + return { + setAttribute: function (key, value) { + this[key] = value; + } + } + }, + getElementsByTagName: function() { + return { + firstChild: { + insertBefore: function() {} + } + } + } + }, + attrs = {'z': 'A', 'y': 2}; + let script = adLoader.loadExternalScript('someUrl', 'criteo', undefined, doc, attrs); + expect(script.z).to.equal('A'); + expect(script.y).to.equal(2); + }); }); diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index b5a4789366a..49ae13c43cc 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -18,10 +18,9 @@ import {find} from 'src/polyfill.js'; import { server } from 'test/mocks/xhr.js'; import {hook} from '../../src/hook.js'; import {auctionManager} from '../../src/auctionManager.js'; -import 'src/debugging.js' // some tests look for debugging side effects +import 'modules/debugging/index.js' // some tests look for debugging side effects import {AuctionIndex} from '../../src/auctionIndex.js'; import {expect} from 'chai'; -import {synchronizePromise} from '../helpers/syncPromise.js'; var assert = require('assert'); @@ -134,7 +133,7 @@ function mockAjaxBuilder() { } describe('auctionmanager.js', function () { - let indexAuctions, indexStub, promiseSandbox; + let indexAuctions, indexStub before(() => { // hooks are global and their side effects depend on what has been loaded @@ -150,13 +149,10 @@ describe('auctionmanager.js', function () { indexAuctions = []; indexStub = sinon.stub(auctionManager, 'index'); indexStub.get(() => new AuctionIndex(() => indexAuctions)); - promiseSandbox = sinon.createSandbox(); - synchronizePromise(promiseSandbox); }); afterEach(() => { indexStub.restore(); - promiseSandbox.restore(); }); describe('getKeyValueTargetingPairs', function () { @@ -682,6 +678,46 @@ describe('auctionmanager.js', function () { }); }); + describe('createAuction', () => { + let adUnits, stubMakeBidRequests, stubCallAdapters + + beforeEach(() => { + stubMakeBidRequests = sinon.stub(adapterManager, 'makeBidRequests').returns([{ + bidderCode: BIDDER_CODE, + bids: [{ + bidder: BIDDER_CODE + }] + }]); + stubCallAdapters = sinon.stub(adapterManager, 'callBids').callsFake((au, reqs, addBid, done) => { + reqs.forEach(r => done.apply(r)); + }); + adUnits = [{ + code: ADUNIT_CODE, + transactionId: ADUNIT_CODE, + bids: [ + {bidder: BIDDER_CODE}, + ] + }]; + }); + + afterEach(() => { + stubMakeBidRequests.restore(); + stubCallAdapters.restore(); + }); + + it('passes global and bidder ortb2 to the auction', () => { + const ortb2Fragments = { + global: {}, + bidder: {} + } + const auction = auctionManager.createAuction({adUnits, ortb2Fragments}); + auction.callBids(); + const anyArgs = [...Array(7).keys()].map(() => sinon.match.any); + sinon.assert.calledWith(stubMakeBidRequests, ...anyArgs.slice(0, 5).concat([sinon.match.same(ortb2Fragments)])); + sinon.assert.calledWith(stubCallAdapters, ...anyArgs.slice(0, 7).concat([sinon.match.same(ortb2Fragments)])); + }); + }); + describe('addBidResponse #1', function () { let createAuctionStub; let adUnits; @@ -1154,8 +1190,7 @@ describe('auctionmanager.js', function () { enabled: true, bidRequests: [{ bidderCode: BIDDER_CODE, - adUnitCode: ADUNIT_CODE, - storedAuctionResponse: '11111' + adUnitCode: ADUNIT_CODE }] } }); @@ -1213,8 +1248,6 @@ describe('auctionmanager.js', function () { const bid = find(auctionBidRequests[0].bids, bid => bid.adUnitCode === ADUNIT_CODE); assert.equal(typeof bid !== 'undefined', true); - assert.equal(bid.hasOwnProperty('storedAuctionResponse'), true); - assert.equal(bid.storedAuctionResponse, '11111'); }); }); @@ -1421,7 +1454,6 @@ describe('auctionmanager.js', function () { } beforeEach(() => { - promiseSandbox.restore(); bids = [ mockBid({bidderCode: BIDDER_CODE1}), mockBid({bidderCode: BIDDER_CODE}) diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 88d6e61c706..d7f6b9de6c0 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -20,9 +20,9 @@ describe('config API', function () { beforeEach(function () { config = newConfig(); - getConfig = config.getConfig; + getConfig = config.getAnyConfig; setConfig = config.setConfig; - readConfig = config.readConfig; + readConfig = config.readAnyConfig; mergeConfig = config.mergeConfig; getBidderConfig = config.getBidderConfig; setBidderConfig = config.setBidderConfig; @@ -106,6 +106,20 @@ describe('config API', function () { sinon.assert.calledOnce(wildcard); }); + it('getConfig subscribers are called immediately if passed {init: true}', () => { + const listener = sinon.spy(); + setConfig({foo: 'bar'}); + getConfig('foo', listener, {init: true}); + sinon.assert.calledWith(listener, {foo: 'bar'}); + }); + + it('getConfig subscribers with no topic are called immediately if passed {init: true}', () => { + const listener = sinon.spy(); + setConfig({foo: 'bar'}); + getConfig(listener, {init: true}); + sinon.assert.calledWith(listener, sinon.match({foo: 'bar'})); + }); + it('sets and gets arbitrary configuration properties', function () { setConfig({ baz: 'qux' }); expect(getConfig('baz')).to.equal('qux'); @@ -130,17 +144,6 @@ describe('config API', function () { expect(getConfig('foo')).to.eql({baz: 'qux'}); }); - it('moves fpd config into ortb2 properties', function () { - setConfig({fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}); - expect(getConfig('ortb2')).to.eql({site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}); - expect(getConfig('fpd')).to.eql(undefined); - }); - - it('moves fpd bidderconfig into ortb2 properties', function () { - setBidderConfig({bidders: ['bidderA'], config: {fpd: {context: {keywords: 'foo,bar', data: {inventory: [1]}}}}}); - expect(getBidderConfig()).to.eql({'bidderA': {ortb2: {site: {keywords: 'foo,bar', ext: {data: {inventory: [1]}}}}}}); - }); - it('sets debugging', function () { setConfig({ debug: true }); expect(getConfig('debug')).to.be.true; @@ -593,6 +596,7 @@ describe('config API', function () { } setConfig({ + bidderTimeout: 2000, ortb2: { user: { data: [userObj1, userObj2] @@ -606,6 +610,7 @@ describe('config API', function () { }); const rtd = { + bidderTimeout: 3000, ortb2: { user: { data: [userObj1] @@ -620,11 +625,13 @@ describe('config API', function () { mergeConfig(rtd); let ortb2Config = getConfig('ortb2'); + let bidderTimeout = getConfig('bidderTimeout'); expect(ortb2Config.user.data).to.deep.include.members([userObj1, userObj2]); expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1]); expect(ortb2Config.user.data).to.have.lengthOf(2); expect(ortb2Config.site.content.data).to.have.lengthOf(1); + expect(bidderTimeout).to.equal(3000); }); it('should not corrupt global configuration with bidder configuration', () => { diff --git a/test/spec/debugging_spec.js b/test/spec/debugging_spec.js index b0f78243e4a..8408ceec367 100644 --- a/test/spec/debugging_spec.js +++ b/test/spec/debugging_spec.js @@ -1,216 +1,93 @@ - -import { expect } from 'chai'; -import { sessionLoader, addBidResponseHook, addBidderRequestsHook, getConfig, disableOverrides, addBidResponseBound, addBidderRequestsBound } from 'src/debugging.js'; -import { addBidResponse, addBidderRequests } from 'src/auction.js'; -import { config } from 'src/config.js'; -import {hook} from '../../src/hook.js'; - -describe('bid overrides', function () { - let sandbox; - - before(() => { - hook.ready(); - }); - - beforeEach(function () { - sandbox = sinon.sandbox.create(); +import {ready, loadSession, getConfig, reset, debuggingModuleLoader, debuggingControls} from '../../src/debugging.js'; +import {getGlobal} from '../../src/prebidGlobal.js'; +import {defer} from '../../src/utils/promise.js'; +import funHooks from 'fun-hooks/no-eval/index.js'; + +describe('Debugging', () => { + beforeEach(() => { + reset(); }); - afterEach(function () { - window.sessionStorage.clear(); - config.resetConfig(); - sandbox.restore(); + after(() => { + reset(); }); - describe('initialization', function () { - beforeEach(function () { - sandbox.stub(config, 'setConfig'); - }); - - afterEach(function () { - disableOverrides(); - }); - - it('should happen when enabled with setConfig', function () { - getConfig({ - enabled: true + describe('module loader', () => { + let script, scriptResult, alreadyInstalled, loader; + beforeEach(() => { + script = sinon.stub().callsFake(() => { + getGlobal()._installDebugging = sinon.stub(); + return scriptResult; }); - - expect(addBidResponse.getHooks().some(hook => hook.hook === addBidResponseBound)).to.equal(true); - expect(addBidderRequests.getHooks().some(hook => hook.hook === addBidderRequestsBound)).to.equal(true); + alreadyInstalled = sinon.stub(); + loader = debuggingModuleLoader({alreadyInstalled, script}); }); - it('should happen when configuration found in sessionStorage', function () { - sessionLoader({ - getItem: () => ('{"enabled": true}') - }); - expect(addBidResponse.getHooks().some(hook => hook.hook === addBidResponseBound)).to.equal(true); - expect(addBidderRequests.getHooks().some(hook => hook.hook === addBidderRequestsBound)).to.equal(true); - }); - - it('should not throw if sessionStorage is inaccessible', function () { - expect(() => { - sessionLoader({ - getItem() { - throw new Error('test'); - } - }); - }).not.to.throw(); - }); - }); - - describe('bidResponse hook', function () { - let mockBids; - let bids; - - beforeEach(function () { - let baseBid = { - 'bidderCode': 'rubicon', - 'width': 970, - 'height': 250, - 'statusMessage': 'Bid available', - 'mediaType': 'banner', - 'source': 'client', - 'currency': 'USD', - 'cpm': 0.5, - 'ttl': 300, - 'netRevenue': false, - 'adUnitCode': '/19968336/header-bid-tag-0' - }; - mockBids = []; - mockBids.push(baseBid); - mockBids.push(Object.assign({}, baseBid, { - bidderCode: 'appnexus' - })); - - bids = []; - }); + afterEach(() => { + delete getGlobal()._installDebugging; + }) - function run(overrides) { - mockBids.forEach(bid => { - let next = (adUnitCode, bid) => { - bids.push(bid); - }; - addBidResponseHook.bind(overrides)(next, bid.adUnitCode, bid); + it('should not attempt to load if debugging module is already installed', () => { + alreadyInstalled.returns(true); + return loader().then(() => { + expect(script.called).to.be.false; }); - } - - it('should allow us to exclude bidders', function () { - run({ - enabled: true, - bidders: ['appnexus'] - }); - - expect(bids.length).to.equal(1); - expect(bids[0].bidderCode).to.equal('appnexus'); }); - it('should allow us to override all bids', function () { - run({ - enabled: true, - bids: [{ - cpm: 2 - }] - }); - - expect(bids.length).to.equal(2); - sinon.assert.match(bids[0], { - cpm: 2, - isDebug: true, - }) - sinon.assert.match(bids[1], { - cpm: 2, - isDebug: true, + it('should not attempt to load twice', () => { + alreadyInstalled.returns(false); + scriptResult = Promise.resolve(); + return Promise.all([loader(), loader()]).then(() => { + expect(script.callCount).to.equal(1); }); }); - it('should allow us to override bids by bidder', function () { - run({ - enabled: true, - bids: [{ - bidder: 'rubicon', - cpm: 2 - }] + it('should call _installDebugging after loading', () => { + alreadyInstalled.returns(false); + scriptResult = Promise.resolve(); + return loader().then(() => { + expect(getGlobal()._installDebugging.called).to.be.true; }); - - expect(bids.length).to.equal(2); - sinon.assert.match(bids[0], { - cpm: 2, - isDebug: true - }); - sinon.assert.match(bids[1], { - cpm: 0.5, - isDebug: sinon.match.falsy - }) }); - it('should allow us to override bids by adUnitCode', function () { - mockBids[1].adUnitCode = 'test'; - - run({ - enabled: true, - bids: [{ - adUnitCode: 'test', - cpm: 2 - }] - }); - - expect(bids.length).to.equal(2); - sinon.assert.match(bids[0], { - cpm: 0.5, - isDebug: sinon.match.falsy, - }); - sinon.assert.match(bids[1], { - cpm: 2, - isDebug: true, + it('should not call _installDebugging if load fails', () => { + const error = new Error(); + alreadyInstalled.returns(false); + scriptResult = Promise.reject(error) + return loader().then(() => { + throw new Error('loader should not resolve'); + }).catch((err) => { + expect(err).to.equal(error); + expect(getGlobal()._installDebugging.called).to.be.false; }); }); }); - describe('bidRequests hook', function () { - let mockBidRequests; - let bidderRequests; - - beforeEach(function () { - let baseBidderRequest = { - 'bidderCode': 'rubicon', - 'bids': [{ - 'width': 970, - 'height': 250, - 'statusMessage': 'Bid available', - 'mediaType': 'banner', - 'source': 'client', - 'currency': 'USD', - 'cpm': 0.5, - 'ttl': 300, - 'netRevenue': false, - 'adUnitCode': '/19968336/header-bid-tag-0' - }] - }; - mockBidRequests = []; - mockBidRequests.push(baseBidderRequest); - mockBidRequests.push(Object.assign({}, baseBidderRequest, { - bidderCode: 'appnexus' - })); - - bidderRequests = []; - }); - - function run(overrides) { - let next = (b) => { - bidderRequests = b; - }; - addBidderRequestsHook.bind(overrides)(next, mockBidRequests); - } - - it('should allow us to exclude bidders', function () { - run({ - enabled: true, - bidders: ['appnexus'] + describe('debugging controls', () => { + let debugging, loader, hook, hookRan; + + beforeEach(() => { + loader = defer(); + hookRan = false; + hook = funHooks()('sync', () => { hookRan = true }); + debugging = debuggingControls({load: sinon.stub().returns(loader.promise), hook}); + }) + + it('should delay execution of hook until module is loaded', () => { + debugging.enable(); + hook(); + expect(hookRan).to.be.false; + loader.resolve(); + return loader.promise.then(() => { + expect(hookRan).to.be.true; }); - - expect(bidderRequests.length).to.equal(1); - expect(bidderRequests[0].bidderCode).to.equal('appnexus'); }); + + it('should restore hook behavior when disabled', () => { + debugging.enable(); + debugging.disable(); + hook(); + expect(hookRan).to.be.true; + }) }); }); diff --git a/test/spec/modules/1plusXRtdProvider_spec.js b/test/spec/modules/1plusXRtdProvider_spec.js new file mode 100644 index 00000000000..9682e4b62f8 --- /dev/null +++ b/test/spec/modules/1plusXRtdProvider_spec.js @@ -0,0 +1,430 @@ +import { config } from 'src/config'; +import { + onePlusXSubmodule, + segtaxes, + extractConfig, + buildOrtb2Updates, + updateBidderConfig, + setTargetingDataToConfig +} from 'modules/1plusXRtdProvider'; + +describe('1plusXRtdProvider', () => { + // Fake server config + let fakeServer; + const fakeResponseHeaders = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }; + const fakeResponse = { + s: ['segment1', 'segment2', 'segment3'], + t: ['targeting1', 'targeting2', 'targeting3'] + }; + + // Bid request config + const reqBidsConfigObj = { + adUnits: [{ + bids: [ + { bidder: 'appnexus' } + ] + }] + }; + + // Bidder configs + const bidderConfigInitial = { + ortb2: { + user: { keywords: '' }, + site: { ext: {} } + } + } + const bidderConfigInitialWith1plusXUserData = { + ortb2: { + user: { + data: [{ name: '1plusX.com', segment: [{ id: 'initial' }] }] + }, + site: { content: { data: [] } } + } + } + const bidderConfigInitialWithUserData = { + ortb2: { + user: { + data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] + }, + site: { content: { data: [] } } + } + } + const bidderConfigInitialWith1plusXSiteContent = { + ortb2: { + user: { data: [] }, + site: { + content: { + data: [{ + name: '1plusX.com', segment: [{ id: 'initial' }], ext: { segtax: 525 } + }] + } + }, + } + } + const bidderConfigInitialWithSiteContent = { + ortb2: { + user: { data: [] }, + site: { + content: { + data: [{ name: 'hello.world', segment: [{ id: 'initial' }] }] + } + }, + } + } + // Util functions + const randomBidder = (len = 5) => Math.random().toString(36).replace(/[^a-z]+/g, '').substring(0, len); + + before(() => { + config.resetConfig(); + }) + + after(() => { }) + + beforeEach(() => { + fakeServer = sinon.createFakeServer(); + fakeServer.respondWith('GET', '*', [200, fakeResponseHeaders, JSON.stringify(fakeResponse)]); + fakeServer.respondImmediately = true; + fakeServer.autoRespond = true; + }) + + describe('onePlusXSubmodule', () => { + it('init is successfull', () => { + const initResult = onePlusXSubmodule.init(); + expect(initResult).to.be.true; + }) + + it('callback is called after getBidRequestData', () => { + // Nice case; everything runs as expected + { + const callbackSpy = sinon.spy(); + const config = { params: { customerId: 'test', bidders: ['appnexus'] } }; + onePlusXSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, config); + setTimeout(() => { + expect(callbackSpy.calledOnce).to.be.true + }, 100) + } + // No customer id in config => error but still callback called + { + const callbackSpy = sinon.spy(); + const config = {} + onePlusXSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, config); + setTimeout(() => { + expect(callbackSpy.calledOnce).to.be.true + }, 100); + } + // No bidders in config => error but still callback called + { + const callbackSpy = sinon.spy(); + const config = { customerId: 'test' } + onePlusXSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, config); + setTimeout(() => { + expect(callbackSpy.calledOnce).to.be.true + }, 100); + } + }) + }) + + describe('extractConfig', () => { + const customerId = 'test'; + const timeout = 1000; + const bidders = ['appnexus']; + + it('Throws an error if no customerId is specified', () => { + const moduleConfig = { params: { timeout, bidders } }; + expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); + }) + it('Throws an error if no bidder is specified', () => { + const moduleConfig = { params: { customerId, timeout } }; + expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); + }) + it("Throws an error if there's no bidder in reqBidsConfigObj", () => { + const moduleConfig = { params: { customerId, timeout, bidders } }; + const reqBidsConfigEmpty = { adUnits: [{ bids: [] }] }; + expect(() => extractConfig(moduleConfig, reqBidsConfigEmpty)).to.throw(); + }) + it('Returns an object containing the parameters specified', () => { + const moduleConfig = { params: { customerId, timeout, bidders } }; + const expectedKeys = ['customerId', 'timeout', 'bidders'] + const extractedConfig = extractConfig(moduleConfig, reqBidsConfigObj); + expect(extractedConfig).to.be.an('object').and.to.have.all.keys(expectedKeys); + expect(extractedConfig.customerId).to.equal(customerId); + expect(extractedConfig.timeout).to.equal(timeout); + expect(extractedConfig.bidders).to.deep.equal(bidders); + }) + /* 1plusX RTD module may only use bidders that are both specified in : + - the bid request configuration + - AND in the 1plusX RTD module configuration + Below 2 tests are enforcing those rules + */ + it('Returns the intersection of bidders found in bid request config & module config', () => { + const bidders = ['appnexus', 'rubicon']; + const moduleConfig = { params: { customerId, timeout, bidders } }; + const { bidders: extractedBidders } = extractConfig(moduleConfig, reqBidsConfigObj); + expect(extractedBidders).to.be.an('array').and.to.have.length(1); 7 + expect(extractedBidders[0]).to.equal('appnexus'); + }) + it('Throws an error if no bidder can be used by the module', () => { + const bidders = ['rubicon']; + const moduleConfig = { params: { customerId, timeout, bidders } }; + expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); + }) + }) + + describe('buildOrtb2Updates', () => { + it('fills site.content.data & user.data in the ortb2 config', () => { + const rtdData = { segments: fakeResponse.s, topics: fakeResponse.t }; + const ortb2Updates = buildOrtb2Updates(rtdData, randomBidder()); + + const expectedOutput = { + siteContentData: { + name: '1plusX.com', + segment: rtdData.topics.map((topicId) => ({ id: topicId })), + ext: { segtax: segtaxes.CONTENT } + }, + userData: { + name: '1plusX.com', + segment: rtdData.segments.map((segmentId) => ({ id: segmentId })) + } + } + expect([ortb2Updates]).to.deep.include.members([expectedOutput]); + }); + it('fills site.keywords in the ortb2 config (appnexus specific)', () => { + const rtdData = { segments: fakeResponse.s, topics: fakeResponse.t }; + const ortb2Updates = buildOrtb2Updates(rtdData, 'appnexus'); + + const expectedOutput = { + site: { + keywords: rtdData.topics.join(','), + } + } + expect([ortb2Updates]).to.deep.include.members([expectedOutput]); + }); + + it('defaults to empty array if no segment is given', () => { + const rtdData = { topics: fakeResponse.t }; + const ortb2Updates = buildOrtb2Updates(rtdData, randomBidder()); + + const expectedOutput = { + siteContentData: { + name: '1plusX.com', + segment: rtdData.topics.map((topicId) => ({ id: topicId })), + ext: { segtax: segtaxes.CONTENT } + }, + userData: { + name: '1plusX.com', + segment: [] + } + } + expect(ortb2Updates).to.deep.include(expectedOutput); + }) + + it('defaults to empty array if no topic is given', () => { + const rtdData = { segments: fakeResponse.s }; + const ortb2Updates = buildOrtb2Updates(rtdData, randomBidder()); + + const expectedOutput = { + siteContentData: { + name: '1plusX.com', + segment: [], + ext: { segtax: segtaxes.CONTENT } + }, + userData: { + name: '1plusX.com', + segment: rtdData.segments.map((segmentId) => ({ id: segmentId })) + } + } + expect(ortb2Updates).to.deep.include(expectedOutput); + }) + it('defaults to empty string if no topic is given (appnexus specific)', () => { + const rtdData = { segments: fakeResponse.s }; + const ortb2Updates = buildOrtb2Updates(rtdData, 'appnexus'); + + const expectedOutput = { + site: { + keywords: '', + } + } + expect(ortb2Updates).to.deep.include(expectedOutput); + }) + }) + + describe('updateBidderConfig', () => { + const ortb2UpdatesAppNexus = { + site: { + keywords: fakeResponse.t.join(','), + }, + userData: { + name: '1plusX.com', + segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) + } + } + const ortb2Updates = { + siteContentData: { + name: '1plusX.com', + segment: fakeResponse.t.map((topicId) => ({ id: topicId })), + ext: { segtax: segtaxes.CONTENT } + }, + userData: { + name: '1plusX.com', + segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) + } + } + + it('merges fetched data in bidderConfig for configured bidders', () => { + const bidder = randomBidder(); + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitial + }); + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); + expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); + // Check that existing config didn't get erased + expect(newBidderConfig.ortb2.site).to.deep.include(bidderConfigInitial.ortb2.site); + expect(newBidderConfig.ortb2.user).to.deep.include(bidderConfigInitial.ortb2.user); + }) + + it('merges fetched data in bidderConfig for configured bidders (appnexus specific)', () => { + const bidder = 'appnexus'; + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitial + }); + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2UpdatesAppNexus, config.getBidderConfig()); + + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.site).to.deep.include(ortb2UpdatesAppNexus.site); + // Check that existing config didn't get erased + expect(newBidderConfig.ortb2.site).to.deep.include(bidderConfigInitial.ortb2.site); + expect(newBidderConfig.ortb2.user).to.deep.include(bidderConfigInitial.ortb2.user); + }) + + it('overwrites an existing 1plus.com entry in ortb2.user.data', () => { + const bidder = randomBidder(); + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitialWith1plusXUserData + }); + // Save previous user.data entry + const previousUserData = bidderConfigInitialWith1plusXUserData.ortb2.user.data[0]; + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); + expect(newBidderConfig.ortb2.user.data).not.to.include(previousUserData); + }) + it("doesn't overwrite entries in ortb2.user.data that aren't 1plusx.com", () => { + const bidder = randomBidder(); + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitialWithUserData + }); + // Save previous user.data entry + const previousUserData = bidderConfigInitialWithUserData.ortb2.user.data[0]; + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.user.data).to.deep.include(ortb2Updates.userData); + expect(newBidderConfig.ortb2.user.data).to.deep.include(previousUserData); + }) + + it('overwrites an existing 1plus.com entry in ortb2.site.content.data', () => { + const bidder = randomBidder(); + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitialWith1plusXSiteContent + }); + // Save previous user.data entry + const previousSiteContent = bidderConfigInitialWith1plusXSiteContent.ortb2.site.content.data[0]; + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); + expect(newBidderConfig.ortb2.site.content.data).not.to.include(previousSiteContent); + }) + it("doesn't overwrite entries in ortb2.site.content.data that aren't 1plusx.com", () => { + const bidder = randomBidder(); + // Set initial config + config.setBidderConfig({ + bidders: [bidder], + config: bidderConfigInitialWithSiteContent + }); + // Save previous user.data entry + const previousSiteContent = bidderConfigInitialWithSiteContent.ortb2.site.content.data[0]; + // Call submodule's setBidderConfig + const newBidderConfig = updateBidderConfig(bidder, ortb2Updates, config.getBidderConfig()); + // Check that the targeting data has been set in the config + expect(newBidderConfig).not.to.be.null; + expect(newBidderConfig.ortb2.site.content.data).to.deep.include(ortb2Updates.siteContentData); + expect(newBidderConfig.ortb2.site.content.data).to.deep.include(previousSiteContent); + }) + }) + + describe('setTargetingDataToConfig', () => { + const expectedKeywords = fakeResponse.t.join(','); + const expectedSiteContentObj = { + data: [{ + name: '1plusX.com', + segment: fakeResponse.t.map((topicId) => ({ id: topicId })), + ext: { segtax: segtaxes.CONTENT } + }] + } + const expectedUserObj = { + data: [{ + name: '1plusX.com', + segment: fakeResponse.s.map((segmentId) => ({ id: segmentId })) + }] + } + const expectedOrtb2 = { + appnexus: { + site: { keywords: expectedKeywords } + }, + rubicon: { + site: { content: expectedSiteContentObj }, + user: expectedUserObj + } + } + + it('sets the config for the selected bidders', () => { + const bidders = ['appnexus', 'rubicon']; + // setting initial config for those bidders + config.setBidderConfig({ + bidders, + config: bidderConfigInitial + }) + // call setTargetingDataToConfig + setTargetingDataToConfig(fakeResponse, { bidders }); + + // Check that the targeting data has been set in both configs + for (const bidder of bidders) { + const newConfig = config.getBidderConfig()[bidder]; + // Check that we got what we expect + const expectedConfErr = (prop) => `New config for ${bidder} doesn't comply with expected at ${prop}`; + expect(newConfig.ortb2.site, expectedConfErr('site')).to.deep.include(expectedOrtb2[bidder].site); + if (expectedOrtb2[bidder].user) { + expect(newConfig.ortb2.user, expectedConfErr('user')).to.deep.include(expectedOrtb2[bidder].user); + } + // Check that existing config didn't get erased + const existingConfErr = (prop) => `Existing config for ${bidder} got unlawfully overwritten at ${prop}`; + expect(newConfig.ortb2.site, existingConfErr('site')).to.deep.include(bidderConfigInitial.ortb2.site); + expect(newConfig.ortb2.user, existingConfErr('user')).to.deep.include(bidderConfigInitial.ortb2.user); + } + }) + }) +}) diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 141edc1e61c..3657f7da912 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -30,6 +30,21 @@ describe('33acrossBidAdapter:', function () { site: { id: siteId }, + device: { + ext: { + ttx: { + w: 1024, + h: 728, + pxr: 2, + vp: { + w: 800, + h: 600 + }, + ah: 500, + mtp: 0 + } + } + }, id: 'r1', regs: { ext: { @@ -117,7 +132,7 @@ describe('33acrossBidAdapter:', function () { this.withProduct = (prod = 'siab') => { ttxRequest.imp.forEach((imp) => { - Object.assign(imp, { + utils.mergeDeep(imp, { ext: { ttx: { prod @@ -129,6 +144,18 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withGpid = (gpid) => { + ttxRequest.imp.forEach((imp) => { + utils.mergeDeep(imp, { + ext: { + gpid + } + }); + }); + + return this; + }; + this.withGdprConsent = (consent, gdpr) => { Object.assign(ttxRequest, { user: { @@ -166,6 +193,12 @@ describe('33acrossBidAdapter:', function () { return this; }; + this.withDevice = (device) => { + utils.mergeDeep(ttxRequest, { device }); + + return this; + }; + this.withPageUrl = pageUrl => { Object.assign(ttxRequest.site, { page: pageUrl @@ -360,8 +393,21 @@ describe('33acrossBidAdapter:', function () { }; win = { parent: null, + devicePixelRatio: 2, + screen: { + width: 1024, + height: 728, + availHeight: 500 + }, + navigator: { + maxTouchPoints: 0 + }, document: { - visibilityState: 'visible' + visibilityState: 'visible', + documentElement: { + clientWidth: 800, + clientHeight: 600 + } }, innerWidth: 800, @@ -373,7 +419,6 @@ describe('33acrossBidAdapter:', function () { .withBanner() .build() ); - sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1); sandbox.stub(document, 'getElementById').returns(element); @@ -387,6 +432,45 @@ describe('33acrossBidAdapter:', function () { describe('isBidRequestValid:', function() { context('basic validation', function() { + it('returns true for valid bidder name values', function() { + const validBidderName = [ + '33across', + '33across_mgni' + ]; + + validBidderName.forEach((bidderName) => { + const bid = { + bidder: bidderName, + params: { + siteId: 'sample33xGUID123456789' + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + }); + + it('returns false for invalid bidder name values', function() { + const invalidBidderName = [ + undefined, + '33', + '33x', + 'thirtythree', + '' + ]; + + invalidBidderName.forEach((bidderName) => { + const bid = { + bidder: bidderName, + params: { + siteId: 'sample33xGUID123456789' + } + }; + + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + it('returns true for valid guid values', function() { // NOTE: We ignore whitespace at the start and end since // in our experience these are common typos @@ -716,6 +800,151 @@ describe('33acrossBidAdapter:', function () { const [ buildRequest ] = spec.buildRequests(bidRequests); validateBuiltServerRequest(buildRequest, serverRequest); }); + + context('when all the wrapping windows are accessible', function() { + it('returns the viewport dimensions of the top most accessible window', function() { + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withDevice({ + ext: { + ttx: { + vp: { + w: 6789, + h: 2345 + } + } + } + }) + .withProduct() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + sandbox.stub(win, 'parent').value({ + document: { + documentElement: { + clientWidth: 1234, + clientHeight: 4567 + } + }, + parent: { + document: { + documentElement: { + clientWidth: 6789, + clientHeight: 2345 + } + }, + } + }); + + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); + }); + }); + + context('when one of the wrapping windows cannot be accessed', function() { + it('returns the viewport dimensions of the top most accessible window', function() { + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withDevice({ + ext: { + ttx: { + vp: { + w: 9876, + h: 5432 + } + } + } + }) + .withProduct() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const notAccessibleParentWindow = {}; + + Object.defineProperty(notAccessibleParentWindow, 'document', { + get() { throw new Error('fakeError'); } + }); + + sandbox.stub(win, 'parent').value({ + document: { + documentElement: { + clientWidth: 1234, + clientHeight: 4567 + } + }, + parent: { + parent: notAccessibleParentWindow, + document: { + documentElement: { + clientWidth: 9876, + clientHeight: 5432 + } + }, + } + }); + + const [ buildRequest ] = spec.buildRequests(bidRequests); + validateBuiltServerRequest(buildRequest, serverRequest); + }); + }); + }); + + it('returns the screen dimensions', function() { + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withDevice({ + ext: { + ttx: { + w: 1024, + h: 728 + } + } + }) + .withProduct() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + win.screen.width = 1024; + win.screen.height = 728; + + const [ buildRequest ] = spec.buildRequests(bidRequests); + + validateBuiltServerRequest(buildRequest, serverRequest); + }); + + context('when the window height is greater than the width', function() { + it('returns the smaller screen dimension as the width', function() { + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withDevice({ + ext: { + ttx: { + w: 728, + h: 1024 + } + } + }) + .withProduct() + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + win.screen.width = 1024; + win.screen.height = 728; + + win.innerHeight = 728; + win.innerWidth = 727; + + const [ buildRequest ] = spec.buildRequests(bidRequests); + + validateBuiltServerRequest(buildRequest, serverRequest); + }); }); context('when tab is inactive', function() { @@ -919,7 +1148,7 @@ describe('33acrossBidAdapter:', function () { it('returns corresponding server requests with site.page set', function() { const bidderRequest = { refererInfo: { - referer: 'http://foo.com/bar' + page: 'http://foo.com/bar' } }; @@ -938,6 +1167,41 @@ describe('33acrossBidAdapter:', function () { }); }); + context('when Global Placement ID (gpid) is defined', function() { + let bidderRequest; + + beforeEach(function() { + bidderRequest = {}; + }); + + it('passes the Global Placement ID (gpid) in the request', function() { + const ttxRequest = new TtxRequestBuilder() + .withBanner() + .withProduct() + .withGpid('fakeGPID0') + .build(); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + + let copyBidRequest = utils.deepClone(bidRequests); + const bidRequestsWithGpid = copyBidRequest.map(function(bidRequest, index) { + return { + ...bidRequest, + ortb2Imp: { + ext: { + gpid: 'fakeGPID' + index + } + } + }; + }); + + const [ builtServerRequest ] = spec.buildRequests(bidRequestsWithGpid, bidderRequest); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); + context('when referer value is not available', function() { it('returns corresponding server requests without site.page set', function() { const bidderRequest = { diff --git a/test/spec/modules/acuityAdsBidAdapter_spec.js b/test/spec/modules/acuityAdsBidAdapter_spec.js new file mode 100644 index 00000000000..18ea574c1ce --- /dev/null +++ b/test/spec/modules/acuityAdsBidAdapter_spec.js @@ -0,0 +1,398 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/acuityAdsBidAdapter'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'acuityads' + +describe('AcuityAdsBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://prebid.admanmedia.com/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.admanmedia.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.admanmedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index a28b6b9be00..36bb5fb1e4d 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -16,7 +16,6 @@ import { loadExternalScript } from '../../../src/adloader.js'; import * as utils from '../../../src/utils.js'; import { config } from '../../../src/config.js'; import { NATIVE } from '../../../src/mediaTypes.js'; -import * as prebidGlobal from 'src/prebidGlobal.js'; import { executeRenderer } from '../../../src/Renderer.js'; const BidRequestBuilder = function BidRequestBuilder(options) { @@ -124,11 +123,18 @@ describe('Adagio bid adapter', () => { adagioMock = sinon.mock(adagio); utilsMock = sinon.mock(utils); + $$PREBID_GLOBAL$$.bidderSettings = { + adagio: { + storageAllowed: true + } + }; + sandbox = sinon.createSandbox(); }); afterEach(() => { window.ADAGIO = undefined; + $$PREBID_GLOBAL$$.bidderSettings = {}; adagioMock.restore(); utilsMock.restore(); @@ -137,24 +143,24 @@ describe('Adagio bid adapter', () => { }); describe('get and set params at adUnit level from global Prebid configuration', function() { - it('should set params get from ortb2 config or bidderSettings. Priority to bidderSetting', function() { + it('should set params get from bid.ortb2', function() { const bid = new BidRequestBuilder().build(); + bid.ortb2 = { + site: { + ext: { + data: { + environment: 'desktop', + pagetype: 'abc' + } + } + } + }; sandbox.stub(config, 'getConfig').callsFake(key => { const config = { adagio: { pagetype: 'article' }, - ortb2: { - site: { - ext: { - data: { - environment: 'desktop', - pagetype: 'abc' - } - } - } - } }; return utils.deepAccess(config, key); }); @@ -1381,21 +1387,14 @@ describe('Adagio bid adapter', () => { describe('site information using refererDetection or window.top', function() { it('should returns domain, page and window.referrer in a window.top context', function() { - sandbox.stub(utils, 'getWindowTop').returns({ - location: { - hostname: 'test.io', - href: 'https://test.io/article/a.html' - }, - document: { - referrer: 'https://google.com' - } - }); - const bidderRequest = new BidderRequestBuilder({ refererInfo: { numIframes: 0, reachedTop: true, - referer: 'https://test.io/article/a.html' + topmostLocation: 'https://test.io/article/a.html', + page: 'https://test.io/article/a.html', + domain: 'test.io', + ref: 'https://google.com' } }).build(); @@ -1418,13 +1417,16 @@ describe('Adagio bid adapter', () => { const info = { numIframes: 0, reachedTop: true, - referer: 'http://level.io/', + page: 'http://level.io/', + topmostLocation: 'http://level.io/', stack: [ 'http://level.io/', 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ], - canonicalUrl: '' + canonicalUrl: '', + domain: 'level.io', + ref: null, }; const bidderRequest = new BidderRequestBuilder({ @@ -1445,13 +1447,16 @@ describe('Adagio bid adapter', () => { const info = { numIframes: 2, reachedTop: false, - referer: 'http://example.com/iframe1.html', + topmostLocation: 'http://example.com/iframe1.html', stack: [ null, 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ], - canonicalUrl: '' + canonicalUrl: '', + page: null, + domain: null, + ref: null }; const bidderRequest = new BidderRequestBuilder({ diff --git a/test/spec/modules/adbookpspBidAdapter_spec.js b/test/spec/modules/adbookpspBidAdapter_spec.js index 3a49f25edb6..f7e401fefbd 100755 --- a/test/spec/modules/adbookpspBidAdapter_spec.js +++ b/test/spec/modules/adbookpspBidAdapter_spec.js @@ -951,7 +951,9 @@ const bidderRequest = { bidderRequestId: '999ccceeee11', timeout: 200, refererInfo: { - referer: 'http://example-domain.com/foo', + page: 'http://mock-page.com', + domain: 'mock-page.com', + ref: 'http://example-domain.com/foo', }, gdprConsent: { gdprApplies: 1, @@ -999,8 +1001,8 @@ const bannerExchangeRequest = { }, }, site: { - domain: location.hostname, - page: location.href, + domain: 'mock-page.com', + page: 'http://mock-page.com', ref: 'http://example-domain.com/foo', }, source: { @@ -1089,8 +1091,8 @@ const videoExchangeRequest = { }, }, site: { - domain: location.hostname, - page: location.href, + domain: 'mock-page.com', + page: 'http://mock-page.com', ref: 'http://example-domain.com/foo', }, source: { @@ -1171,8 +1173,8 @@ const mixedExchangeRequest = { }, }, site: { - domain: location.hostname, - page: location.href, + domain: 'mock-page.com', + page: 'http://mock-page.com', ref: 'http://example-domain.com/foo', }, source: { diff --git a/test/spec/modules/addefendBidAdapter_spec.js b/test/spec/modules/addefendBidAdapter_spec.js index ac01750e98f..b3b6b2d417a 100644 --- a/test/spec/modules/addefendBidAdapter_spec.js +++ b/test/spec/modules/addefendBidAdapter_spec.js @@ -25,7 +25,7 @@ describe('addefendBidAdapter', () => { return spec.buildRequests(buildRequest, { ...bidderRequest || {}, refererInfo: { - referer: 'https://referer.example.com' + page: 'https://referer.example.com' } })[0]; }; diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index 4317fd3258b..b1c2d0b4ad2 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -71,7 +71,7 @@ describe('Adf adapter', function () { adxDomain: '10.8.57.207' } }]; - let request = spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }); + let request = spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }); assert.equal(request.method, 'POST'); assert.equal(request.url, 'https://10.8.57.207/adx/openrtb'); @@ -82,7 +82,7 @@ describe('Adf adapter', function () { describe('user privacy', function () { it('should send GDPR Consent data to adform if gdprApplies', function () { let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; - let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; + let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { page: 'page' } }; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); assert.equal(request.user.ext.consent, bidderRequest.gdprConsent.consentString); @@ -92,7 +92,7 @@ describe('Adf adapter', function () { it('should send gdpr as number', function () { let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; - let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; + let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { page: 'page' } }; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); assert.equal(typeof request.regs.ext.gdpr, 'number'); @@ -101,12 +101,12 @@ describe('Adf adapter', function () { it('should send CCPA Consent data to adform', function () { let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; - let bidderRequest = { uspConsent: '1YA-', refererInfo: { referer: 'page' } }; + let bidderRequest = { uspConsent: '1YA-', refererInfo: { page: 'page' } }; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); assert.equal(request.regs.ext.us_privacy, '1YA-'); - bidderRequest = { uspConsent: '1YA-', gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; + bidderRequest = { uspConsent: '1YA-', gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { page: 'page' } }; request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); assert.equal(request.regs.ext.us_privacy, '1YA-'); @@ -119,13 +119,13 @@ describe('Adf adapter', function () { bidId: 'bidId', params: { siteId: 'siteId' } }]; - let bidderRequest = {gdprConsent: {gdprApplies: false, consentString: 'consentDataString'}, refererInfo: { referer: 'page' }}; + let bidderRequest = { gdprConsent: {gdprApplies: false, consentString: 'consentDataString'}, refererInfo: { page: 'page' } }; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); assert.equal(request.user.ext.consent, 'consentDataString'); assert.equal(request.regs.ext.gdpr, 0); - bidderRequest = {gdprConsent: {consentString: 'consentDataString'}, refererInfo: { referer: 'page' }}; + bidderRequest = {gdprConsent: {consentString: 'consentDataString'}, refererInfo: { page: 'page' }}; request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); assert.equal(request.user, undefined); @@ -136,7 +136,7 @@ describe('Adf adapter', function () { bidId: 'bidId', params: { siteId: 'siteId' } }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); assert.equal(request.user, undefined); assert.equal(request.regs, undefined); @@ -148,7 +148,7 @@ describe('Adf adapter', function () { bidId: 'bidId', params: { test: 1 } }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); assert.ok(request.is_debug); assert.equal(request.test, 1); @@ -160,7 +160,7 @@ describe('Adf adapter', function () { bidId: 'bidId', params: { siteId: 'siteId' } }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); let data = Object.keys(request); assert.deepEqual(keys, data); @@ -172,7 +172,7 @@ describe('Adf adapter', function () { params: { siteId: 'siteId' }, transactionId: 'transactionId' }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); assert.equal(request.source.tid, validBidRequests[0].transactionId); assert.equal(request.source.fd, 1); @@ -182,7 +182,7 @@ describe('Adf adapter', function () { config.setConfig({ }); let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; - let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { referer: 'page' } }; + let bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, refererInfo: { page: 'page' } }; let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); assert.equal(request.regs.coppa, undefined); @@ -200,7 +200,7 @@ describe('Adf adapter', function () { coppa: true }); let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); assert.equal(request.regs.coppa, 1); }); @@ -213,7 +213,7 @@ describe('Adf adapter', function () { bidId: 'bidId', params: { mid: '1000' } }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); assert.equal(request.device.ua, navigator.userAgent); assert.equal(request.device.w, 100); @@ -223,13 +223,14 @@ describe('Adf adapter', function () { it('should send app info', function () { config.setConfig({ app: { id: 'appid' }, - ortb2: { app: { name: 'appname' } } }); + const ortb2 = { app: { name: 'appname' } }; let validBidRequests = [{ bidId: 'bidId', - params: { mid: '1000' } + params: { mid: '1000' }, + ortb2 }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' }, ortb2 }).data); assert.equal(request.app.id, 'appid'); assert.equal(request.app.name, 'appname'); @@ -244,23 +245,24 @@ describe('Adf adapter', function () { domain: 'publisher.domain.com' } }, - ortb2: { - site: { - publisher: { - name: 'publisher\'s name' - } + }); + const ortb2 = { + site: { + publisher: { + name: 'publisher\'s name' } } - }); + }; let validBidRequests = [{ bidId: 'bidId', - params: { mid: '1000' } + params: { mid: '1000' }, + ortb2 }]; - let refererInfo = { referer: 'page' }; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo }).data); + let refererInfo = { page: 'page' }; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo, ortb2 }).data); assert.deepEqual(request.site, { - page: refererInfo.referer, + page: refererInfo.page, publisher: { domain: 'publisher.domain.com', name: 'publisher\'s name' @@ -279,7 +281,7 @@ describe('Adf adapter', function () { }) }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); assert.deepEqual(request.user.ext.eids, [ { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } @@ -289,7 +291,7 @@ describe('Adf adapter', function () { it('should send currency if defined', function () { config.setConfig({ currency: { adServerCurrency: 'EUR' } }); let validBidRequests = [{ params: {} }]; - let refererInfo = { referer: 'page' }; + let refererInfo = { page: 'page' }; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo }).data); assert.deepEqual(request.cur, [ 'EUR' ]); @@ -307,7 +309,7 @@ describe('Adf adapter', function () { } }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); assert.deepEqual(request.source.ext.schain, { validation: 'strict', config: { @@ -322,7 +324,7 @@ describe('Adf adapter', function () { bidId: 'bidId', params: { siteId: 'siteId' } }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); assert.equal(request.ext.pt, 'net'); }); @@ -331,7 +333,7 @@ describe('Adf adapter', function () { bidId: 'bidId', params: { priceType: 'net' } }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); assert.equal(request.ext.pt, 'net'); }); @@ -346,7 +348,7 @@ describe('Adf adapter', function () { bidId: 'bidId2', params: { siteId: 'siteId' } }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); assert.equal(request.imp.length, 2); }); @@ -364,7 +366,7 @@ describe('Adf adapter', function () { params: { mid: '1000' }, mediaTypes: {video: {}} }]; - let imps = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; + let imps = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp; for (let i = 0; i < 3; i++) { assert.equal(imps[i].id, i + 1); @@ -375,7 +377,7 @@ describe('Adf adapter', function () { let validBidRequests = [{ bidId: 'bidId', params: {mid: 1000}, mediaTypes: {video: {}} }, { bidId: 'bidId2', params: {mid: 1001}, mediaTypes: {video: {}} }, { bidId: 'bidId3', params: {mid: 1002}, mediaTypes: {video: {}} }]; - let imps = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; + let imps = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp; for (let i = 0; i < 3; i++) { assert.equal(imps[i].tagid, validBidRequests[i].params.mid); } @@ -488,7 +490,7 @@ describe('Adf adapter', function () { video: {} } }]; - let [ first, second, third ] = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; + let [ first, second, third ] = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp; assert.ok(first.banner); assert.ok(first.video); @@ -515,7 +517,7 @@ describe('Adf adapter', function () { } } }]; - let { banner } = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0]; + let { banner } = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0]; assert.deepEqual(banner, { format: [ { w: 100, h: 100 }, { w: 200, h: 300 } ] }); @@ -535,7 +537,7 @@ describe('Adf adapter', function () { } } }]; - let { video } = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0]; + let { video } = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0]; assert.deepEqual(video, { playerSize: [640, 480], context: 'outstream', @@ -556,7 +558,7 @@ describe('Adf adapter', function () { body: { len: 140 } } }]; - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.equal(assets[0].id, 0); assert.equal(assets[1].id, 3); @@ -574,7 +576,7 @@ describe('Adf adapter', function () { } }]; - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.equal(assets[0].required, 1); assert.ok(!assets[1].required); @@ -597,7 +599,7 @@ describe('Adf adapter', function () { } }]; - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.ok(assets[0].title); assert.equal(assets[0].title.len, 140); assert.deepEqual(assets[1].img, { type: 3, w: 150, h: 50 }); @@ -620,7 +622,7 @@ describe('Adf adapter', function () { } }]; - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.ok(assets[0].img); assert.equal(assets[0].img.w, 200); assert.equal(assets[0].img.h, 300); @@ -649,7 +651,7 @@ describe('Adf adapter', function () { } }]; - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.ok(assets[0].img); assert.equal(assets[0].img.wmin, 100); assert.equal(assets[0].img.hmin, 300); @@ -673,7 +675,7 @@ describe('Adf adapter', function () { } }]; - assert.doesNotThrow(() => spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } })); + assert.doesNotThrow(() => spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } })); }); }); @@ -691,7 +693,7 @@ describe('Adf adapter', function () { } }]; - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp[0].native.request.assets; assert.ok(assets[0].img); assert.equal(assets[0].img.wmin, 0); assert.equal(assets[0].img.hmin, 0); @@ -701,7 +703,7 @@ describe('Adf adapter', function () { }); function getRequestImps(validBidRequests) { - return JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp; + return JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp; } }); diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index 030aa448c19..8583c226e50 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -101,18 +101,55 @@ describe('AdgenerationAdapter', function () { snowflake: {'id': 'novatiqId', syncResponse: 1} } } + }, + { // bannerWithAdgextCriteoId + bidder: 'adg', + params: { + id: '58278', // banner + }, + adUnitCode: 'adunit-code', + sizes: [[320, 100]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a', + userId: { + criteoId: 'criteo-id-test-1234567890' + } + }, + { // bannerWithAdgextId5Id + bidder: 'adg', + params: { + id: '58278', // banner + }, + adUnitCode: 'adunit-code', + sizes: [[320, 100]], + bidId: '2f6ac468a9c15e', + bidderRequestId: '14a9f773e30243', + auctionId: '4aae9f05-18c6-4fcd-80cf-282708cd584a', + transactionTd: 'f76f6dfd-d64f-4645-a29f-682bac7f431a', + userId: { + id5id: { + ext: { + linkType: 2 + }, + uid: 'id5-id-test-1234567890' + } + } } ]; const bidderRequest = { refererInfo: { - referer: 'https://example.com' + page: 'https://example.com' } }; const data = { - banner: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.3.0&imark=1&tp=https%3A%2F%2Fexample.com`, - bannerUSD: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=USD&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.3.0&imark=1&tp=https%3A%2F%2Fexample.com`, - native: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=1x1¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.3.0&tp=https%3A%2F%2Fexample.com`, - bannerWithHyperId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.3.0&imark=1&tp=https%3A%2F%2Fexample.com&hyper_id=novatiqId`, + banner: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&imark=1&tp=https%3A%2F%2Fexample.com`, + bannerUSD: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=USD&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&imark=1&tp=https%3A%2F%2Fexample.com`, + native: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=1x1¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&tp=https%3A%2F%2Fexample.com`, + bannerWithHyperId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&imark=1&tp=https%3A%2F%2Fexample.com&hyper_id=novatiqId`, + bannerWithAdgextCriteoId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&adgext_criteo_id=criteo-id-test-1234567890&imark=1&tp=https%3A%2F%2Fexample.com`, + bannerWithAdgextId5Id: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.4.0&adgext_id5_id=id5-id-test-1234567890&adgext_id5_id_link_type=2&imark=1&tp=https%3A%2F%2Fexample.com`, }; it('sends bid request to ENDPOINT via GET', function () { const request = spec.buildRequests(bidRequests, bidderRequest)[0]; @@ -149,6 +186,17 @@ describe('AdgenerationAdapter', function () { }); expect(request.data).to.equal(data.bannerWithHyperId); }); + + it('should attache params to the bannerWithAdgextCriteoId request', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[3]; + expect(request.data).to.equal(data.bannerWithAdgextCriteoId); + }); + + it('should attache params to the bannerWithAdgextId5Id request', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[4]; + expect(request.data).to.equal(data.bannerWithAdgextId5Id); + }); + it('allows setConfig to set bidder currency for JPY', function () { config.setConfig({ currency: { diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index 3fe0a62b2a0..a2e2691c8ba 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -67,7 +67,7 @@ describe('AdheseAdapter', function () { consentString: 'CONSENT_STRING' }, refererInfo: { - referer: 'http://prebid.org/dev-docs/subjects?_d=1' + page: 'http://prebid.org/dev-docs/subjects?_d=1' } }; diff --git a/test/spec/modules/adkernelAdnBidAdapter_spec.js b/test/spec/modules/adkernelAdnBidAdapter_spec.js index c4ad134711a..67c168f5063 100644 --- a/test/spec/modules/adkernelAdnBidAdapter_spec.js +++ b/test/spec/modules/adkernelAdnBidAdapter_spec.js @@ -144,7 +144,7 @@ describe('AdkernelAdn adapter', function () { auctionStart: 1545836987704, timeout: 3000, refererInfo: { - referer: 'https://example.com/index.html', + page: 'https://example.com/index.html', reachedTop: true, numIframes: 0, stack: ['https://example.com/index.html'] diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index ab1c5501bd9..45498d2734a 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -3,6 +3,7 @@ import {spec} from 'modules/adkernelBidAdapter'; import * as utils from 'src/utils'; import {NATIVE, BANNER, VIDEO} from 'src/mediaTypes'; import {config} from 'src/config'; +import {parseDomain} from '../../../src/refererDetection.js'; describe('Adkernel adapter', function () { const bid1_zone1 = { @@ -16,6 +17,9 @@ describe('Adkernel adapter', function () { banner: { sizes: [[300, 250], [300, 200]] } + }, + ortb2Imp: { + battr: [6, 7, 9] } }, bid2_zone2 = { bidder: 'adkernel', @@ -94,12 +98,12 @@ describe('Adkernel adapter', function () { params: { zoneId: 1, host: 'rtb.adkernel.com', - video: {api: [1, 2]} }, mediaTypes: { video: { context: 'instream', - playerSize: [[640, 480]] + playerSize: [[640, 480]], + api: [1, 2] } }, adUnitCode: 'ad-unit-1' @@ -253,7 +257,7 @@ describe('Adkernel adapter', function () { }); function buildBidderRequest(url = 'https://example.com/index.html', params = {}) { - return Object.assign({}, params, {refererInfo: {referer: url, reachedTop: true}, timeout: 3000, bidderCode: 'adkernel'}); + return Object.assign({}, params, {refererInfo: {page: url, domain: parseDomain(url), reachedTop: true}, timeout: 3000, bidderCode: 'adkernel'}); } const DEFAULT_BIDDER_REQUEST = buildBidderRequest(); @@ -292,6 +296,7 @@ describe('Adkernel adapter', function () { describe('banner request building', function () { let bidRequest, bidRequests, _; + before(function () { [_, bidRequests] = buildRequest([bid1_zone1]); bidRequest = bidRequests[0]; @@ -336,6 +341,11 @@ describe('Adkernel adapter', function () { expect(bidRequest.device).to.have.property('dnt', 1); }); + it('should copy FPD to imp.banner', function() { + expect(bidRequest.imp[0].banner).to.have.property('battr'); + expect(bidRequest.imp[0].banner.battr).to.be.eql([6, 7, 9]); + }); + it('shouldn\'t contain gdpr nor ccpa information for default request', function () { let [_, bidRequests] = buildRequest([bid1_zone1]); expect(bidRequests[0]).to.not.have.property('regs'); diff --git a/test/spec/modules/adlooxRtdProvider_spec.js b/test/spec/modules/adlooxRtdProvider_spec.js index b576ffb9f3b..236e053e58c 100644 --- a/test/spec/modules/adlooxRtdProvider_spec.js +++ b/test/spec/modules/adlooxRtdProvider_spec.js @@ -171,6 +171,7 @@ describe('Adloox RTD Provider', function () { }); it('should fetch segments', function (done) { + const req = {}; const adUnitWithSegments = utils.deepClone(adUnit); const getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ adUnits: [ adUnitWithSegments ] @@ -180,10 +181,11 @@ describe('Adloox RTD Provider', function () { expect(ret).is.true; const callback = function () { - expect(__config.ortb2.site.ext.data.adloox_rtd.ok).is.true; - expect(__config.ortb2.site.ext.data.adloox_rtd.nope).is.undefined; - expect(__config.ortb2.user.ext.data.adloox_rtd.unused).is.false; - expect(__config.ortb2.user.ext.data.adloox_rtd.nope).is.undefined; + const ortb2 = req.ortb2Fragments.global; + expect(ortb2.site.ext.data.adloox_rtd.ok).is.true; + expect(ortb2.site.ext.data.adloox_rtd.nope).is.undefined; + expect(ortb2.user.ext.data.adloox_rtd.unused).is.false; + expect(ortb2.user.ext.data.adloox_rtd.nope).is.undefined; expect(adUnitWithSegments.ortb2Imp.ext.data.adloox_rtd.dis.length).is.equal(3); expect(adUnitWithSegments.ortb2Imp.ext.data.adloox_rtd.nope).is.undefined; @@ -191,7 +193,7 @@ describe('Adloox RTD Provider', function () { done(); }; - rtdProvider.getBidRequestData({}, callback, CONFIG, null); + rtdProvider.getBidRequestData(req, callback, CONFIG, null); const request = server.requests[0]; const response = { unused: false, _: [ { d: 77 } ] }; @@ -207,7 +209,11 @@ describe('Adloox RTD Provider', function () { adUnits: [ adUnitWithSegments ] }); - const targetingData = rtdProvider.getTargetingData([ adUnitWithSegments.code ], CONFIG); + const targetingData = rtdProvider.getTargetingData([ adUnitWithSegments.code ], CONFIG, null, { + getFPD: () => ({ + global: __config.ortb2 + }) + }); expect(Object.keys(targetingData).length).is.equal(1); expect(Object.keys(targetingData[adUnit.code]).length).is.equal(2); expect(targetingData[adUnit.code].adl_ok).is.equal(1); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 6dfde0d0652..228b87ae4d5 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -61,7 +61,7 @@ describe('AdmixerAdapter', function () { let bidderRequest = { bidderCode: BIDDER_CODE, refererInfo: { - referer: 'https://example.com' + page: 'https://example.com' } }; diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 25b72216395..2d5ea630f0f 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -11,20 +11,34 @@ describe('adnuntiusBidAdapter', function () { const GVLID = 855; const usi = utils.generateUUID() const meta = [{ key: 'usi', value: usi }] - const storage = getStorageManager({gvlid: GVLID, moduleName: 'adnuntius'}) - storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) + + before(() => { + const storage = getStorageManager({ gvlid: GVLID, moduleName: 'adnuntius' }) + storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) + }); + + beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + adnuntius: { + storageAllowed: true + } + }; + }); afterEach(function () { config.resetConfig(); + $$PREBID_GLOBAL$$.bidderSettings = {}; }); + const tzo = new Date().getTimezoneOffset(); const ENDPOINT_URL = `${URL}${tzo}&format=json&userId=${usi}`; + const ENDPOINT_URL_VIDEO = `${URL}${tzo}&format=json&userId=${usi}&tt=vast4`; const ENDPOINT_URL_NOCOOKIE = `${URL}${tzo}&format=json&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${URL}${tzo}&format=json&segments=segment1,segment2,segment3&userId=${usi}`; const ENDPOINT_URL_CONSENT = `${URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; const adapter = newBidder(spec); - const bidRequests = [ + const bidderRequests = [ { bidId: '123', bidder: 'adnuntius', @@ -32,7 +46,28 @@ describe('adnuntiusBidAdapter', function () { auId: '8b6bc', network: 'adnuntius', }, + mediaTypes: { + banner: { + sizes: [[640, 480], [600, 400]], + } + }, + } + ] + const videoBidderRequest = [ + { + bidId: '123', + bidder: 'adnuntius', + params: { + auId: '8b6bc', + network: 'adnuntius', + }, + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, } ] @@ -44,6 +79,10 @@ describe('adnuntiusBidAdapter', function () { ] } + const videoBidRequest = { + bid: videoBidderRequest + } + const serverResponse = { body: { 'adUnits': [ @@ -107,6 +146,69 @@ describe('adnuntiusBidAdapter', function () { ] } } + const serverVideoResponse = { + body: { + 'adUnits': [ + { + 'auId': '000000000008b6bc', + 'targetId': '123', + 'html': '

hi!

', + 'matchedAdCount': 1, + 'responseId': 'adn-rsp-1460129238', + 'ads': [ + { + 'destinationUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com', + 'assets': { + 'image': { + 'cdnId': 'https://assets.adnuntius.com/oEmZa5uYjxENfA1R692FVn6qIveFpO8wUbpyF2xSOCc.jpg', + 'width': '980', + 'height': '120' + } + }, + 'clickUrl': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'urls': { + 'destination': 'https://delivery.adnuntius.com/c/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN?ct=2501&r=http%3A%2F%2Fgoogle.com' + }, + 'urlsEsc': { + 'destination': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fc%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN%3Fct%3D2501%26r%3Dhttp%253A%252F%252Fgoogle.com' + }, + 'destinationUrls': { + 'destination': 'http://google.com' + }, + 'cpm': { 'amount': 5.0, 'currency': 'NOK' }, + 'bid': { 'amount': 0.005, 'currency': 'NOK' }, + 'cost': { 'amount': 0.005, 'currency': 'NOK' }, + 'impressionTrackingUrls': [], + 'impressionTrackingUrlsEsc': [], + 'adId': 'adn-id-1347343135', + 'selectedColumn': '0', + 'selectedColumnPosition': '0', + 'renderedPixel': 'https://delivery.adnuntius.com/b/52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + 'renderedPixelEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fb%2F52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN.html', + 'visibleUrl': 'https://delivery.adnuntius.com/s?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'visibleUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fs%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'viewUrl': 'https://delivery.adnuntius.com/v?rt=52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'viewUrlEsc': 'https%3A%2F%2Fdelivery.adnuntius.com%2Fv%3Frt%3D52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'rt': '52AHNuxCqxB_Y9ZP9ERWkMBPCOha4zuV3aKn5cog5jsAAAAQCtjQz9kbGWD4nuZy3q6HaHGLB4-k_fySWECIOOmHKY6iokgHNFH-U57ew_-1QHlKnFr2NT8y4QK1oU5HxnDLbYPz-GmQ3C2JyxLGpKmIb-P-3bm7HYPEreNjPdhjRG51A8NGuc4huUhns7nEUejHuOjOHE5sV1zfYxCRWRx9wPDN9EUCC7KN', + 'creativeWidth': '980', + 'creativeHeight': '120', + 'creativeId': 'wgkq587vgtpchsx1', + 'lineItemId': 'scyjdyv3mzgdsnpf', + 'layoutId': 'sw6gtws2rdj1kwby', + 'layoutName': 'Responsive image' + }, + + ] + }, + { + 'auId': '000000000008b6bc', + 'targetId': '456', + 'matchedAdCount': 0, + 'responseId': 'adn-rsp-1460129238', + } + ] + } + } describe('inherited functions', function () { it('exists and is a function', function () { @@ -116,13 +218,13 @@ describe('adnuntiusBidAdapter', function () { describe('isBidRequestValid', function () { it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bidRequests[0])).to.equal(true); + expect(spec.isBidRequestValid(bidderRequests[0])).to.equal(true); }); }); describe('buildRequests', function () { it('Test requests', function () { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidderRequests, {}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('bid'); const bid = request[0].bid[0] @@ -130,56 +232,56 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\",\"targetId\":\"123\"}],\"metaData\":{\"usi\":\"' + usi + '\"}}'); + expect(request[0].data).to.equal('{\"adUnits\":[{\"auId\":\"8b6bc\",\"targetId\":\"123\",\"dimensions\":[[640,480],[600,400]]}],\"metaData\":{\"usi\":\"' + usi + '\"}}'); + }); + + it('Test Video requests', function () { + const request = spec.buildRequests(videoBidderRequest, {}); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('bid'); + const bid = request[0].bid[0] + expect(bid).to.have.property('bidId'); + expect(request[0]).to.have.property('url'); + expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO); }); it('should pass segments if available in config', function () { - config.setBidderConfig({ - bidders: ['adnuntius', 'other'], - config: { - ortb2: { - user: { - data: [{ - name: 'adnuntius', - segment: [{ id: 'segment1' }, { id: 'segment2' }] - }, - { - name: 'other', - segment: ['segment3'] - }], - } - } + const ortb2 = { + user: { + data: [{ + name: 'adnuntius', + segment: [{ id: 'segment1' }, { id: 'segment2' }] + }, + { + name: 'other', + segment: ['segment3'] + }], } - }); + }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); it('should skip segments in config if not either id or array of strings', function () { - config.setBidderConfig({ - bidders: ['adnuntius', 'other'], - config: { - ortb2: { - user: { - data: [{ - name: 'adnuntius', - segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] - }, - { - name: 'other', - segment: [{ - notright: 'segment4' - }] - }], - } - } + const ortb2 = { + user: { + data: [{ + name: 'adnuntius', + segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] + }, + { + name: 'other', + segment: [{ + notright: 'segment4' + }] + }], } - }); + }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); @@ -188,84 +290,69 @@ describe('adnuntiusBidAdapter', function () { describe('user privacy', function () { it('should send GDPR Consent data if gdprApplies', function () { - let request = spec.buildRequests(bidRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); }); it('should not send GDPR Consent data if gdprApplies equals undefined', function () { - let request = spec.buildRequests(bidRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); }); it('should pass segments if available in config', function () { - config.setBidderConfig({ - bidders: ['adnuntius', 'other'], - config: { - ortb2: { - user: { - data: [{ - name: 'adnuntius', - segment: [{ id: 'segment1' }, { id: 'segment2' }] - }, - { - name: 'other', - segment: ['segment3'] - }], - } - } + const ortb2 = { + user: { + data: [{ + name: 'adnuntius', + segment: [{ id: 'segment1' }, { id: 'segment2' }] + }, + { + name: 'other', + segment: ['segment3'] + }], } - }); + } - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); it('should skip segments in config if not either id or array of strings', function () { - config.setBidderConfig({ - bidders: ['adnuntius', 'other'], - config: { - ortb2: { - user: { - data: [{ - name: 'adnuntius', - segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] - }, - { - name: 'other', - segment: [{ - notright: 'segment4' - }] - }], - } - } + const ortb2 = { + user: { + data: [{ + name: 'adnuntius', + segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] + }, + { + name: 'other', + segment: [{ + notright: 'segment4' + }] + }], } - }); + }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); it('should user user ID if present in ortb2.user.id field', function () { - config.setBidderConfig({ - bidders: ['adnuntius', 'other'], - config: { - ortb2: { - user: { - id: usi - } - } + const ortb2 = { + user: { + id: usi } - }); + }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); @@ -274,14 +361,14 @@ describe('adnuntiusBidAdapter', function () { describe('user privacy', function () { it('should send GDPR Consent data if gdprApplies', function () { - let request = spec.buildRequests(bidRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); }); it('should not send GDPR Consent data if gdprApplies equals undefined', function () { - let request = spec.buildRequests(bidRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); @@ -297,7 +384,7 @@ describe('adnuntiusBidAdapter', function () { } }); - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {})); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_NOCOOKIE); @@ -322,4 +409,21 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].ttl).to.equal(360); }); }); + describe('interpretVideoResponse', function () { + it('should return valid response when passed valid server response', function () { + const interpretedResponse = spec.interpretResponse(serverVideoResponse, videoBidRequest); + const ad = serverVideoResponse.body.adUnits[0].ads[0] + expect(interpretedResponse).to.have.lengthOf(1); + expect(interpretedResponse[0].cpm).to.equal(ad.cpm.amount); + expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[0].currency).to.equal(ad.bid.currency); + expect(interpretedResponse[0].netRevenue).to.equal(false); + expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); + expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('google.com'); + expect(interpretedResponse[0].vastXml).to.equal(serverVideoResponse.body.adUnits[0].vastXml); + }); + }); }); diff --git a/test/spec/modules/adoceanBidAdapter_spec.js b/test/spec/modules/adoceanBidAdapter_spec.js index ba691825112..080b5bd5d1d 100644 --- a/test/spec/modules/adoceanBidAdapter_spec.js +++ b/test/spec/modules/adoceanBidAdapter_spec.js @@ -136,10 +136,12 @@ describe('AdoceanAdapter', function () { expect(request.url).to.include('gdpr_consent=' + bidderRequest.gdprConsent.consentString); }); - it('should attach sizes information to url', function () { + it('should attach sizes and slaves information to url', function () { let requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.include('aosspsizes=myaozpniqismex~300x250_300x600'); + expect(requests[0].url).to.include('slaves=zpniqismex'); expect(requests[1].url).to.include('aosspsizes=myaozpniqismex~300x200_600x250'); + expect(requests[1].url).to.include('slaves=zpniqismex'); const differentSlavesBids = deepClone(bidRequests); differentSlavesBids[1].params.slaveId = 'adoceanmyaowafpdwlrks'; @@ -147,6 +149,7 @@ describe('AdoceanAdapter', function () { expect(requests.length).to.equal(1); expect(requests[0].url).to.include('aosspsizes=myaozpniqismex~300x250_300x600-myaowafpdwlrks~300x200_600x250'); expect((requests[0].url.match(/aosspsizes=/g) || []).length).to.equal(1); + expect(requests[0].url).to.include('slaves=zpniqismex,wafpdwlrks'); }); it('should attach schain parameter if available', function() { diff --git a/test/spec/modules/adotBidAdapter_spec.js b/test/spec/modules/adotBidAdapter_spec.js index 81b9c5e15e9..34252e00f9e 100644 --- a/test/spec/modules/adotBidAdapter_spec.js +++ b/test/spec/modules/adotBidAdapter_spec.js @@ -28,7 +28,7 @@ describe('Adot Adapter', function () { it('should build request (banner)', function () { const bidderRequestId = 'bidderRequestId'; const validBidRequests = [{ bidderRequestId, mediaTypes: {} }, { bidderRequestId, bidId: 'bidId', mediaTypes: { banner: { sizes: [[300, 250]] } }, params: { placementId: 'placementId', adUnitCode: 200 } }]; - const bidderRequest = { position: 2, refererInfo: { referer: 'http://localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true } }; + const bidderRequest = { position: 2, refererInfo: { page: 'http://localhost.com', domain: 'localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true }, userId: { pubProvidedId: 'userId' }, schain: { ver: '1.0' } }; const request = spec.buildRequests(validBidRequests, bidderRequest); const buildBidRequestResponse = { @@ -48,16 +48,17 @@ describe('Adot Adapter', function () { bidfloor: 0 }], site: { - page: bidderRequest.refererInfo.referer, + page: bidderRequest.refererInfo.page, domain: 'localhost.com', name: 'localhost.com', publisher: { // id: 'adot' id: undefined - } + }, + ext: { schain: { ver: '1.0' } } }, device: { ua: navigator.userAgent, language: navigator.language }, - user: { ext: { consent: bidderRequest.gdprConsent.consentString } }, + user: { ext: { consent: bidderRequest.gdprConsent.consentString, pubProvidedId: 'userId' } }, regs: { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies } }, ext: { adot: { adapter_version: 'v2.0.0' }, @@ -76,7 +77,7 @@ describe('Adot Adapter', function () { it('should build request (native)', function () { const bidderRequestId = 'bidderRequestId'; const validBidRequests = [{ bidderRequestId, mediaTypes: {} }, { bidderRequestId, bidId: 'bidId', mediaTypes: { native: { title: { required: true, len: 50, sizes: [[300, 250]] }, wrong: {}, image: {} } }, params: { placementId: 'placementId', adUnitCode: 200 } }]; - const bidderRequest = { position: 2, refererInfo: { referer: 'http://localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true } }; + const bidderRequest = { position: 2, refererInfo: { page: 'http://localhost.com', domain: 'localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true }, userId: { pubProvidedId: 'userId' }, schain: { ver: '1.0' } }; const request = spec.buildRequests(validBidRequests, bidderRequest); const buildBidRequestResponse = { @@ -95,16 +96,17 @@ describe('Adot Adapter', function () { bidfloor: 0 }], site: { - page: bidderRequest.refererInfo.referer, + page: bidderRequest.refererInfo.page, domain: 'localhost.com', name: 'localhost.com', publisher: { // id: 'adot' id: undefined - } + }, + ext: { schain: { ver: '1.0' } } }, device: { ua: navigator.userAgent, language: navigator.language }, - user: { ext: { consent: bidderRequest.gdprConsent.consentString } }, + user: { ext: { consent: bidderRequest.gdprConsent.consentString, pubProvidedId: 'userId' } }, regs: { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies } }, ext: { adot: { adapter_version: 'v2.0.0' }, @@ -123,7 +125,7 @@ describe('Adot Adapter', function () { it('should build request (video)', function () { const bidderRequestId = 'bidderRequestId'; const validBidRequests = [{ bidderRequestId, mediaTypes: {} }, { bidderRequestId, bidId: 'bidId', mediaTypes: { video: { playerSize: [[300, 250]], minduration: 1, maxduration: 2, api: 'api', linearity: 'linearity', mimes: [], placement: 'placement', playbackmethod: 'playbackmethod', protocols: 'protocol', startdelay: 'startdelay' } }, params: { placementId: 'placementId', adUnitCode: 200 } }]; - const bidderRequest = { position: 2, refererInfo: { referer: 'http://localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true } }; + const bidderRequest = { position: 2, refererInfo: { page: 'http://localhost.com', domain: 'localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true }, userId: { pubProvidedId: 'userId' }, schain: { ver: '1.0' } }; const request = spec.buildRequests(validBidRequests, bidderRequest); const buildBidRequestResponse = { @@ -154,16 +156,17 @@ describe('Adot Adapter', function () { bidfloor: 0 }], site: { - page: bidderRequest.refererInfo.referer, + page: bidderRequest.refererInfo.page, domain: 'localhost.com', name: 'localhost.com', publisher: { // id: 'adot' id: undefined - } + }, + ext: { schain: { ver: '1.0' } } }, device: { ua: navigator.userAgent, language: navigator.language }, - user: { ext: { consent: bidderRequest.gdprConsent.consentString } }, + user: { ext: { consent: bidderRequest.gdprConsent.consentString, pubProvidedId: 'userId' } }, regs: { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies } }, ext: { adot: { adapter_version: 'v2.0.0' }, diff --git a/test/spec/modules/adpartnerBidAdapter_spec.js b/test/spec/modules/adpartnerBidAdapter_spec.js index 94b56f7735b..d9f9b0d0074 100644 --- a/test/spec/modules/adpartnerBidAdapter_spec.js +++ b/test/spec/modules/adpartnerBidAdapter_spec.js @@ -86,7 +86,7 @@ describe('AdpartnerAdapter', function () { let bidderRequest = { refererInfo: { - referer: 'https://test.domain' + page: 'https://test.domain' } }; diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js index d25fdaf86d7..2612800aa56 100644 --- a/test/spec/modules/adrelevantisBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -69,7 +69,7 @@ describe('AdrelevantisAdapter', function () { } ); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {}); const payload = JSON.parse(request.data); expect(payload.tags[0].private_sizes).to.exist; @@ -77,7 +77,7 @@ describe('AdrelevantisAdapter', function () { }); it('should add source and verison to the tag', function () { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, {}); const payload = JSON.parse(request.data); expect(payload.sdk).to.exist; expect(payload.sdk).to.deep.equal({ @@ -92,7 +92,7 @@ describe('AdrelevantisAdapter', function () { bidRequest.mediaTypes = {}; bidRequest.mediaTypes[type] = {}; - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {}); const payload = JSON.parse(request.data); expect(payload.tags[0].ad_types).to.deep.equal([type]); @@ -104,14 +104,14 @@ describe('AdrelevantisAdapter', function () { bidRequest.mediaTypes = {}; bidRequest.mediaTypes.video = {context: 'outstream'}; - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {}); const payload = JSON.parse(request.data); expect(payload.tags[0].ad_types).to.deep.equal(['video']); }); it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, {}); expect(request.url).to.equal(ENDPOINT); expect(request.method).to.equal('POST'); }); @@ -131,7 +131,7 @@ describe('AdrelevantisAdapter', function () { } ); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {}); const payload = JSON.parse(request.data); expect(payload.tags[0].video).to.deep.equal({ id: 123, @@ -168,7 +168,7 @@ describe('AdrelevantisAdapter', function () { bidRequest2.adUnitCode = 'adUnit_code_2'; bidRequest2 = Object.assign({}, bidRequest2, videoData); - const request = spec.buildRequests([bidRequest1, bidRequest2]); + const request = spec.buildRequests([bidRequest1, bidRequest2], {}); const payload = JSON.parse(request.data); expect(payload.tags[0].video).to.deep.equal({ skippable: true, @@ -195,7 +195,7 @@ describe('AdrelevantisAdapter', function () { } ); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {}); const payload = JSON.parse(request.data); expect(payload.user).to.exist; @@ -215,32 +215,27 @@ describe('AdrelevantisAdapter', function () { } } ); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {}); const payload = JSON.parse(request.data); expect(payload.tags[0].hb_source).to.deep.equal(1); }); it('adds context data (category and keywords) to request when set', function() { let bidRequest = Object.assign({}, bidRequests[0]); - sinon - .stub(config, 'getConfig') - .withArgs('ortb2') - .returns({ - site: { - keywords: 'US Open', - ext: { - data: {category: 'sports/tennis'} - } + const ortb2 = { + site: { + keywords: 'US Open', + ext: { + data: {category: 'sports/tennis'} } - }); + } + }; - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {ortb2}); const payload = JSON.parse(request.data); expect(payload.fpd.keywords).to.equal('US Open'); expect(payload.fpd.category).to.equal('sports/tennis'); - - config.getConfig.restore(); }); it('should attach native params to the request', function () { @@ -269,7 +264,7 @@ describe('AdrelevantisAdapter', function () { } ); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {}); const payload = JSON.parse(request.data); expect(payload.tags[0].native.layouts[0]).to.deep.equal({ @@ -306,13 +301,13 @@ describe('AdrelevantisAdapter', function () { ); bidRequest.sizes = [[150, 100], [300, 250]]; - let request = spec.buildRequests([bidRequest]); + let request = spec.buildRequests([bidRequest], {}); let payload = JSON.parse(request.data); expect(payload.tags[0].sizes).to.deep.equal([{width: 150, height: 100}, {width: 300, height: 250}]); delete bidRequest.sizes; - request = spec.buildRequests([bidRequest]); + request = spec.buildRequests([bidRequest], {}); payload = JSON.parse(request.data); expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); @@ -338,7 +333,7 @@ describe('AdrelevantisAdapter', function () { } ); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {}); const payload = JSON.parse(request.data); expect(payload.tags[0].keywords).to.deep.equal([{ @@ -374,7 +369,7 @@ describe('AdrelevantisAdapter', function () { } ); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {}); const payload = JSON.parse(request.data); expect(payload.tags[0].use_pmt_rule).to.equal(true); @@ -425,7 +420,7 @@ describe('AdrelevantisAdapter', function () { } } ); - const request = spec.buildRequests([appRequest]); + const request = spec.buildRequests([appRequest], {}); const payload = JSON.parse(request.data); expect(payload.app).to.exist; expect(payload.app).to.deep.equal({ @@ -450,7 +445,7 @@ describe('AdrelevantisAdapter', function () { const bidRequest = Object.assign({}, bidRequests[0]) const bidderRequest = { refererInfo: { - referer: 'http://example.com/page.html', + topmostLocation: 'http://example.com/page.html', reachedTop: true, numIframes: 2, stack: [ @@ -478,7 +473,7 @@ describe('AdrelevantisAdapter', function () { .withArgs('coppa') .returns(true); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], {}); const payload = JSON.parse(request.data); expect(payload.user.coppa).to.equal(true); diff --git a/test/spec/modules/adrinoBidAdapter_spec.js b/test/spec/modules/adrinoBidAdapter_spec.js index 52b2796e6db..78cea8da9ac 100644 --- a/test/spec/modules/adrinoBidAdapter_spec.js +++ b/test/spec/modules/adrinoBidAdapter_spec.js @@ -1,8 +1,13 @@ import { expect } from 'chai'; import { spec } from 'modules/adrinoBidAdapter.js'; +import {config} from '../../../src/config.js'; import * as utils from '../../../src/utils'; describe('adrinoBidAdapter', function () { + afterEach(() => { + config.resetConfig(); + }); + describe('isBidRequestValid', function () { const validBid = { bidder: 'adrino', @@ -66,16 +71,37 @@ describe('adrinoBidAdapter', function () { } } }, + userId: { criteoId: '2xqi3F94aHdwWnM3', pubcid: '3ec0b202-7697' }, adUnitCode: 'adunit-code', bidId: '12345678901234', bidderRequestId: '98765432109876', auctionId: '01234567891234', }; + it('should build the request correctly with custom domain', function () { + config.setConfig({adrino: { host: 'https://stg-prebid-bidder.adrino.io' }}); + const result = spec.buildRequests( + [ bidRequest ], + { refererInfo: { page: 'http://example.com/' } } + ); + expect(result.length).to.equal(1); + expect(result[0].method).to.equal('POST'); + expect(result[0].url).to.equal('https://stg-prebid-bidder.adrino.io/bidder/bid/'); + expect(result[0].data.bidId).to.equal('12345678901234'); + expect(result[0].data.placementHash).to.equal('abcdef123456'); + expect(result[0].data.referer).to.equal('http://example.com/'); + expect(result[0].data.userAgent).to.equal(navigator.userAgent); + expect(result[0].data).to.have.property('nativeParams'); + expect(result[0].data).not.to.have.property('gdprConsent'); + expect(result[0].data).to.have.property('userId'); + expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); + }); + it('should build the request correctly with gdpr', function () { const result = spec.buildRequests( [ bidRequest ], - { gdprConsent: { gdprApplies: true, consentString: 'abc123' }, refererInfo: { referer: 'http://example.com/' } } + { gdprConsent: { gdprApplies: true, consentString: 'abc123' }, refererInfo: { page: 'http://example.com/' } } ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); @@ -86,12 +112,15 @@ describe('adrinoBidAdapter', function () { expect(result[0].data.userAgent).to.equal(navigator.userAgent); expect(result[0].data).to.have.property('nativeParams'); expect(result[0].data).to.have.property('gdprConsent'); + expect(result[0].data).to.have.property('userId'); + expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); }); it('should build the request correctly without gdpr', function () { const result = spec.buildRequests( [ bidRequest ], - { refererInfo: { referer: 'http://example.com/' } } + { refererInfo: { page: 'http://example.com/' } } ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); @@ -102,6 +131,9 @@ describe('adrinoBidAdapter', function () { expect(result[0].data.userAgent).to.equal(navigator.userAgent); expect(result[0].data).to.have.property('nativeParams'); expect(result[0].data).not.to.have.property('gdprConsent'); + expect(result[0].data).to.have.property('userId'); + expect(result[0].data.userId.criteoId).to.equal('2xqi3F94aHdwWnM3'); + expect(result[0].data.userId.pubcid).to.equal('3ec0b202-7697'); }); }); diff --git a/test/spec/modules/adriverBidAdapter_spec.js b/test/spec/modules/adriverBidAdapter_spec.js index 12c0a15fb06..33084877c14 100644 --- a/test/spec/modules/adriverBidAdapter_spec.js +++ b/test/spec/modules/adriverBidAdapter_spec.js @@ -297,25 +297,18 @@ describe('adriverAdapter', function () { { adrcid: undefined } ] cookieValues.forEach(cookieValue => describe('test cookie exist or not behavior', function () { - let expectedValues = { - adrcid: cookieValue.adrcid, - at: '', - cur: '', - tmax: '', - site: '', - id: '', - user: '', - device: '', - imp: '' - } + let expectedValues = [ + 'buyerid', + 'ext' + ] it('check adrcid if it exists', function () { bidRequests[0].userId.adrcid = cookieValue.adrcid; const payload = JSON.parse(spec.buildRequests(bidRequests).data); if (cookieValue.adrcid) { - expect(Object.keys(payload)).to.have.members(Object.keys(expectedValues)); + expect(Object.keys(payload.user)).to.have.members(expectedValues); } else { - expect(payload.adrcid).to.equal(undefined); + expect(payload.user.buyerid).to.equal(0); } }); })); diff --git a/test/spec/modules/adriverIdSystem_spec.js b/test/spec/modules/adriverIdSystem_spec.js index 29d965d5ed4..abc831b67f0 100644 --- a/test/spec/modules/adriverIdSystem_spec.js +++ b/test/spec/modules/adriverIdSystem_spec.js @@ -32,7 +32,6 @@ describe('AdriverIdSystem', function () { expect(request.url).to.include('https://ad.adriver.ru/cgi-bin/json.cgi'); request.respond(503, null, 'Unavailable'); expect(logErrorStub.calledOnce).to.be.true; - expect(callbackSpy.calledOnce).to.be.true; }); it('test call user sync url with the right params', function() { diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index 2600b7c4595..d0ef69ccf08 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -19,6 +19,8 @@ const aliasEP = { streamkey: 'https://ghb.hb.streamkey.net/v2/auction/', janet: 'https://ghb.bidder.jmgads.com/v2/auction/', pgam: 'https://ghb.pgamssp.com/v2/auction/', + ocm: 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', + vidcrunchllc: 'https://ghb.platform.vidcrunch.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; diff --git a/test/spec/modules/adtrgtmeBidAdapter_spec.js b/test/spec/modules/adtrgtmeBidAdapter_spec.js new file mode 100644 index 00000000000..fce270b4ea7 --- /dev/null +++ b/test/spec/modules/adtrgtmeBidAdapter_spec.js @@ -0,0 +1,802 @@ +import { expect } from 'chai'; +import { config } from 'src/config.js'; +import { BANNER } from 'src/mediaTypes.js'; +import { spec } from 'modules/adtrgtmeBidAdapter.js'; + +const DEFAULT_SID = 1220291391; +const DEFAULT_ZID = 1836455615; +const DEFAULT_BID_ID = '84ab500420319d'; + +const DEFAULT_AD_UNIT_CODE = '/1220291391/header-banner'; +const DEFAULT_AD_UNIT_TYPE = BANNER; +const DEFAULT_PARAMS_BID_OVERRIDE = {}; + +const ADAPTER_VERSION = '1.0.0'; +const PREBID_VERSION = '$prebid.version$'; +const INTEGRATION_METHOD = 'prebid.js'; + +// Utility functions +const generateBidRequest = ({bidId, adUnitCode, bidOverrideObject, zid, ortb2}) => { + const bidRequest = { + adUnitCode, + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId, + bidderRequestsCount: 1, + bidder: 'adtrgtme', + bidderRequestId: '7101db09af0db2', + bidderWinsCount: 0, + mediaTypes: {}, + params: { + bidOverride: bidOverrideObject + }, + src: 'client', + transactionId: '5b17b67d-7704-4732-8cc9-5b1723e9bcf9', + ortb2 + }; + + const bannerObj = { + sizes: [[300, 250]] + }; + + bidRequest.mediaTypes.banner = bannerObj; + bidRequest.sizes = [[300, 250]]; + + bidRequest.params.sid = DEFAULT_SID; + if (typeof zid == 'number') { + bidRequest.params.zid = zid; + } + + return bidRequest; +} + +let generateBidderRequest = (bidRequestArray, adUnitCode, ortb2 = {}) => { + const bidderRequest = { + adUnitCode: adUnitCode || 'default-adUnitCode', + auctionId: 'd4c83a3b-18e4-4208-b98b-63848449c7aa', + auctionStart: new Date().getTime(), + bidderCode: 'adtrgtme', + bidderRequestId: '112f1c7c5d399a', + bids: bidRequestArray, + refererInfo: { + page: 'https://publisher-test.com', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['https://publisher-test.com'], + }, + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + vendorData: {}, + gdprApplies: true + }, + start: new Date().getTime(), + timeout: 1000, + ortb2 + }; + + return bidderRequest; +}; + +const generateBuildRequestMock = ({bidId, adUnitCode, adUnitType, zid, bidOverrideObject, pubIdMode, ortb2}) => { + const bidRequestConfig = { + bidId: bidId || DEFAULT_BID_ID, + adUnitCode: adUnitCode || DEFAULT_AD_UNIT_CODE, + adUnitType: adUnitType || DEFAULT_AD_UNIT_TYPE, + zid: zid || DEFAULT_ZID, + bidOverrideObject: bidOverrideObject || DEFAULT_PARAMS_BID_OVERRIDE, + + pubIdMode: pubIdMode || false, + ortb2: ortb2 || {} + }; + const bidRequest = generateBidRequest(bidRequestConfig); + const validBidRequests = [bidRequest]; + const bidderRequest = generateBidderRequest(validBidRequests, adUnitCode, ortb2); + + return { bidRequest, validBidRequests, bidderRequest } +}; + +const generateAdmPayload = (admPayloadType) => { + let ADM_PAYLOAD; + switch (admPayloadType) { + case 'banner': + ADM_PAYLOAD = ''; // banner + break; + default: ''; break; + }; + + return ADM_PAYLOAD; +}; + +const generateResponseMock = (admPayloadType) => { + const bidResponse = { + id: 'fc0c35df-21fb-4f93-9ebd-88759dbe31f9', + impid: '274395c06a24e5', + adm: generateAdmPayload(admPayloadType), + price: 1, + w: 300, + h: 250, + crid: 'ssp-placement-name', + adomain: ['advertiser-domain.com'] + }; + + const serverResponse = { + body: { + id: 'fc0c35df-21fb-4f93-9ebd-88759dbe31f9', + seatbid: [{ bid: [ bidResponse ], seat: 13107 }] + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: admPayloadType}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + + return {serverResponse, data, bidderRequest}; +} + +// Unit tests +describe('adtrgtme Bid Adapter:', () => { + it('PLACEHOLDER TO PASS GULP', () => { + const obj = {}; + expect(obj).to.be.an('object'); + }); + + describe('Validate basic properties', () => { + it('should define the correct bidder code', () => { + expect(spec.code).to.equal('adtrgtme') + }); + }); + + describe('getUserSyncs()', () => { + const IMAGE_PIXEL_URL = 'http://image-pixel.com/foo/bar?1234&baz=true'; + const IFRAME_ONE_URL = 'http://image-iframe.com/foo/bar?1234&baz=true'; + const IFRAME_TWO_URL = 'http://image-iframe-two.com/foo/bar?1234&baz=true'; + + let serverResponses = []; + beforeEach(() => { + serverResponses[0] = { + body: { + ext: { + pixels: `` + } + } + } + }); + + after(() => { + serverResponses = undefined; + }); + + it('for only iframe enabled syncs', () => { + let syncOptions = { + iframeEnabled: true, + pixelEnabled: false + }; + let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); + expect(pixelsObjects.length).to.equal(2); + expect(pixelsObjects).to.deep.equal( + [ + {type: 'iframe', 'url': IFRAME_ONE_URL}, + {type: 'iframe', 'url': IFRAME_TWO_URL} + ] + ) + }); + + it('for only pixel enabled syncs', () => { + let syncOptions = { + iframeEnabled: false, + pixelEnabled: true + }; + let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); + expect(pixelsObjects.length).to.equal(1); + expect(pixelsObjects).to.deep.equal( + [ + {type: 'image', 'url': IMAGE_PIXEL_URL} + ] + ) + }); + + it('for both pixel and iframe enabled syncs', () => { + let syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); + expect(pixelsObjects.length).to.equal(3); + expect(pixelsObjects).to.deep.equal( + [ + {type: 'iframe', 'url': IFRAME_ONE_URL}, + {type: 'image', 'url': IMAGE_PIXEL_URL}, + {type: 'iframe', 'url': IFRAME_TWO_URL} + ] + ) + }); + }); + + // Validate Bid Requests + describe('isBidRequestValid()', () => { + const INVALID_INPUT = [ + {}, + {params: {}}, + {params: {sid: '1234', zid: '4321'}}, + {params: {sid: '1220291391', zid: 4321}}, + {params: {zid: ''}}, + {params: {sid: '', zid: ''}}, + ]; + + INVALID_INPUT.forEach(input => { + it(`should determine that the bid is INVALID for the input ${JSON.stringify(input)}`, () => { + expect(spec.isBidRequestValid(input)).to.be.false; + }); + }); + + it('should determine that the bid is VALID if sid and zid are present on the params object', () => { + const validBid = { + params: { + sid: 1220291391, + zid: 1836455615 + } + }; + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + }); + + describe('Price Floor module support:', () => { + it('should get bidfloor from getFloor method', () => { + const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); + bidRequest.params.bidOverride = {cur: 'EUR'}; + bidRequest.getFloor = (floorObj) => { + return { + floor: bidRequest.floors.values[floorObj.mediaType + '|300x250'], + currency: floorObj.currency, + mediaType: floorObj.mediaType + } + }; + bidRequest.floors = { + currency: 'EUR', + values: { + 'banner|300x250': 5.55 + } + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.cur).to.deep.equal(['EUR']); + expect(data.imp[0].bidfloor).is.a('number'); + expect(data.imp[0].bidfloor).to.equal(5.55); + }); + }); + + describe('Schain module support:', () => { + it('should send Global or Bidder specific schain', function () { + const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const globalSchain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'some-platform.com', + sid: '111111', + rid: bidRequest.bidId, + hp: 1 + }] + }; + bidRequest.schain = globalSchain; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const schain = data.source.ext.schain; + expect(schain.nodes.length).to.equal(1); + expect(schain).to.equal(globalSchain); + }); + }); + + describe('First party data module - "Site" support (ortb2):', () => { + // Should not allow invalid "site" data types + const INVALID_ORTB2_TYPES = [ null, [], 123, 'unsupportedKeyName', true, false, undefined ]; + INVALID_ORTB2_TYPES.forEach(param => { + it(`should not allow invalid site types to be added to bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { site: param } + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site[param]).to.be.undefined; + }); + }); + + // Should add valid "site" params + const VALID_SITE_STRINGS = ['name', 'domain', 'page', 'ref', 'keywords']; + const VALID_SITE_ARRAYS = ['cat', 'sectioncat', 'pagecat']; + + VALID_SITE_STRINGS.forEach(param => { + it(`should allow supported site keys to be added bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + [param]: 'something' + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site[param]).to.exist; + expect(data.site[param]).to.be.a('string'); + expect(data.site[param]).to.be.equal(ortb2.site[param]); + }); + }); + + VALID_SITE_ARRAYS.forEach(param => { + it(`should determine that the ortb2.site Array key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + [param]: ['something'] + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site[param]).to.exist; + expect(data.site[param]).to.be.a('array'); + expect(data.site[param]).to.be.equal(ortb2.site[param]); + }); + }); + + // Should not allow invalid "site.content" data types + INVALID_ORTB2_TYPES.forEach(param => { + it(`should determine that the ortb2.site.content key is invalid and should not be added to bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + content: param + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.content).to.be.undefined; + }); + }); + + // Should not allow invalid "site.content" keys + it(`should not allow invalid ortb2.site.content object keys to be added to bid-request: {custom object}`, () => { + const ortb2 = { + site: { + content: { + fake: 'news', + unreal: 'param', + counterfit: 'data' + } + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.content).to.be.a('object'); + }); + + // Should append valid "site.content" keys + const VALID_CONTENT_STRINGS = ['id', 'title', 'language']; + VALID_CONTENT_STRINGS.forEach(param => { + it(`should determine that the ortb2.site String key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + content: { + [param]: 'something' + } + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.content[param]).to.exist; + expect(data.site.content[param]).to.be.a('string'); + expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); + }); + }); + + const VALID_CONTENT_ARRAYS = ['cat']; + VALID_CONTENT_ARRAYS.forEach(param => { + it(`should determine that the ortb2.site Array key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + site: { + content: { + [param]: ['something', 'something-else'] + } + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.site.content[param]).to.be.a('array'); + expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); + }); + }); + }); + + describe('First party data module - "User" support (ortb2):', () => { + // Global ortb2.user validations + // Should not allow invalid "user" data types + const INVALID_ORTB2_TYPES = [ null, [], 'unsupportedKeyName', true, false, undefined ]; + INVALID_ORTB2_TYPES.forEach(param => { + it(`should not allow invalid site types to be added to bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { user: param } + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user[param]).to.be.undefined; + }); + }); + + // Should add valid "user" params + const VALID_USER_STRINGS = ['id', 'buyeruid', 'gender', 'keywords', 'customdata']; + VALID_USER_STRINGS.forEach(param => { + it(`should allow supported user string keys to be added bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + user: { + [param]: 'something' + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user[param]).to.exist; + expect(data.user[param]).to.be.a('string'); + expect(data.user[param]).to.be.equal(ortb2.user[param]); + }); + }); + + const VALID_USER_OBJECTS = ['ext']; + VALID_USER_OBJECTS.forEach(param => { + it(`should allow supported user extObject keys to be added to the bid-request: ${JSON.stringify(param)}`, () => { + const ortb2 = { + user: { + [param]: {a: '123', b: '456'} + } + }; + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.user[param]).to.exist; + expect(data.user[param]).to.be.a('object'); + expect(data.user[param]).to.be.deep.include({[param]: {a: '123', b: '456'}}); + config.setConfig({ortb2: {}}); + }); + }); + + // adUnit.ortb2Imp.ext.data + it(`should allow adUnit.ortb2Imp.ext.data object to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + ext: { + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123' + } + } + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].ext.data).to.deep.equal(validBidRequests[0].ortb2Imp.ext.data); + }); + // adUnit.ortb2Imp.instl + it(`should allow adUnit.ortb2Imp.instl numeric boolean "1" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: 1 + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.deep.equal(validBidRequests[0].ortb2Imp.instl); + }); + + it(`should prevent adUnit.ortb2Imp.instl boolean "true" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: true + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.not.exist; + }); + + it(`should prevent adUnit.ortb2Imp.instl boolean "false" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: false + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.not.exist; + }); + }); + + describe('GDPR & Consent:', () => { + it('should return request objects that do not send cookies if purpose 1 consent is not provided', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + bidderRequest.gdprConsent = { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + apiVersion: 2, + vendorData: { + purpose: { + consents: { + '1': false + } + } + }, + gdprApplies: true + }; + const options = spec.buildRequests(validBidRequests, bidderRequest)[0].options; + expect(options.withCredentials).to.be.false; + }); + }); + + describe('Endpoint & Impression Request Mode:', () => { + it('should route request to config override endpoint', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const sid = validBidRequests[0].params.sid; + const testOverrideEndpoint = 'http://new_bidder_host.com/ssp?s='; + config.setConfig({ + adtrgtme: { + endpoint: testOverrideEndpoint + } + }); + const response = spec.buildRequests(validBidRequests, bidderRequest)[0]; + expect(response).to.deep.include( + { + method: 'POST', + url: testOverrideEndpoint + sid + }); + }); + + it('should route request to endpoint + sid', () => { + config.setConfig({ + adtrgtme: {} + }); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const sid = validBidRequests[0].params.sid; + const response = spec.buildRequests(validBidRequests, bidderRequest); + expect(response[0]).to.deep.include({ + method: 'POST', + url: 'https://z.cdn.adtarget.market/ssp?prebid&s=' + sid + }); + }); + + it('should return a single request object for single request mode', () => { + let { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const BID_ID_2 = '84ab50xxxxx'; + const BID_ZID_2 = 98876543210; + const AD_UNIT_CODE_2 = 'test-ad-unit-code-123'; + const { bidRequest: bidRequest2 } = generateBuildRequestMock({bidId: BID_ID_2, zid: BID_ZID_2, adUnitCode: AD_UNIT_CODE_2}); + validBidRequests = [bidRequest, bidRequest2]; + bidderRequest.bids = validBidRequests; + + config.setConfig({ + adtrgtme: { + singleRequestMode: true + } + }); + + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + + expect(data.imp).to.be.an('array').with.lengthOf(2); + + expect(data.imp[0]).to.deep.include({ + id: DEFAULT_BID_ID, + ext: { + dfp_ad_unit_code: DEFAULT_AD_UNIT_CODE + } + }); + + expect(data.imp[1]).to.deep.include({ + id: BID_ID_2, + tagid: BID_ZID_2, + ext: { + dfp_ad_unit_code: AD_UNIT_CODE_2 + } + }); + }); + }); + + describe('Validate request filtering:', () => { + it('should not return request when no bids are present', function () { + let request = spec.buildRequests([]); + expect(request).to.be.undefined; + }); + + it('buildRequests(): should return an array with the correct amount of request objects', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const response = spec.buildRequests(validBidRequests, bidderRequest).bidderRequest; + expect(response.bids).to.be.an('array').to.have.lengthOf(1); + }); + }); + + describe('Request Headers validation:', () => { + it('should return request objects with the relevant custom headers and content type declaration', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + bidderRequest.gdprConsent.gdprApplies = false; + const options = spec.buildRequests(validBidRequests, bidderRequest).options; + expect(options).to.deep.equal( + { + contentType: 'application/json', + customHeaders: { + 'x-openrtb-version': '2.5' + }, + withCredentials: true + }); + }); + }); + + describe('Request Payload oRTB bid validation:', () => { + it('should generate a valid openRTB bid-request object in the data field', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.site).to.deep.include({ + id: bidderRequest.bids[0].params.sid, + page: bidderRequest.refererInfo.page + }); + + expect(data.device).to.deep.equal({ + dnt: 0, + ua: navigator.userAgent, + ip: undefined + }); + + expect(data.regs).to.deep.equal({ + ext: { + 'us_privacy': '', + gdpr: 1 + } + }); + + expect(data.source).to.deep.equal({ + ext: { + hb: 1, + adapterver: ADAPTER_VERSION, + prebidver: PREBID_VERSION, + integration: { + name: INTEGRATION_METHOD, + ver: PREBID_VERSION + } + }, + fd: 1 + }); + + expect(data.cur).to.deep.equal(['USD']); + }); + + it('should generate a valid openRTB imp.ext object in the bid-request', () => { + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const bid = validBidRequests[0]; + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.imp[0].ext).to.deep.equal({ + dfp_ad_unit_code: DEFAULT_AD_UNIT_CODE + }); + }); + + it('should use siteId value as site.id in the outbound bid-request when using "pubId" integration mode', () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true}); + validBidRequests[0].params.sid = 9876543210; + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.site.id).to.equal(9876543210); + }); + + it('should use placementId value as imp.tagid in the outbound bid-request when using "zid"', () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}), + TEST_ZID = 54321; + validBidRequests[0].params.zid = TEST_ZID; + const data = spec.buildRequests(validBidRequests, bidderRequest).data; + expect(data.imp[0].tagid).to.deep.equal(TEST_ZID); + }); + }); + + describe('Request Payload oRTB bid.imp validation:', () => { + // Validate Banner imp imp when adtrgtme.mode=undefined + it('should generate a valid "Banner" imp object', () => { + config.setConfig({ + adtrgtme: {} + }); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].banner).to.deep.equal({ + mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: [{w: 300, h: 250}] + }); + }); + + // Validate Banner imp + it('should generate a valid "Banner" imp object', () => { + config.setConfig({ + adtrgtme: { mode: 'banner' } + }); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].banner).to.deep.equal({ + mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], + format: [{w: 300, h: 250}] + }); + }); + }); + + describe('interpretResponse()', () => { + describe('for mediaTypes: "banner"', () => { + it('should insert banner payload into response[0].ad', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ad).to.equal(''); + expect(response[0].mediaType).to.equal('banner'); + }) + }); + + describe('Support Advertiser domains', () => { + it('should append bid-response adomain to meta.advertiserDomains', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].meta.advertiserDomains).to.be.a('array'); + expect(response[0].meta.advertiserDomains[0]).to.equal('advertiser-domain.com'); + }) + }); + + describe('bid response Ad ID / Creative ID', () => { + it('should use adId if it exists in the bid-response', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const adId = 'bid-response-adId'; + serverResponse.body.seatbid[0].bid[0].adId = adId; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].adId).to.equal(adId); + }); + + it('should use impid if adId does not exist in the bid-response', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const impid = '25b6c429c1f52f'; + serverResponse.body.seatbid[0].bid[0].impid = impid; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].adId).to.equal(impid); + }); + + it('should use crid if adId & impid do not exist in the bid-response', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const crid = 'passback-12579'; + serverResponse.body.seatbid[0].bid[0].impid = undefined; + serverResponse.body.seatbid[0].bid[0].crid = crid; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].adId).to.equal(crid); + }); + }); + + describe('Time To Live (ttl)', () => { + const UNSUPPORTED_TTL_FORMATS = ['string', [1, 2, 3], true, false, null, undefined]; + UNSUPPORTED_TTL_FORMATS.forEach(param => { + it('should not allow unsupported global adtrgtme.ttl formats and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + config.setConfig({ + adtrgtme: { ttl: param } + }); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); + }); + + it('should not allow unsupported params.ttl formats and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + bidderRequest.bids[0].params.ttl = param; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); + }); + }); + + const UNSUPPORTED_TTL_VALUES = [-1, 3601]; + UNSUPPORTED_TTL_VALUES.forEach(param => { + it('should not allow invalid global adtrgtme.ttl values 3600 < ttl < 0 and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + config.setConfig({ + adtrgtme: { ttl: param } + }); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); + }); + + it('should not allow invalid params.ttl values 3600 < ttl < 0 and default to 300', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + bidderRequest.bids[0].params.ttl = param; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(300); + }); + }); + + it('should give presedence to Gloabl ttl over params.ttl ', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + config.setConfig({ + adtrgtme: { ttl: 500 } + }); + bidderRequest.bids[0].params.ttl = 400; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].ttl).to.equal(500); + }); + }); + + describe('Aliasing support', () => { + it('should return undefined as the bidder code value', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(response[0].bidderCode).to.be.undefined; + }); + }); + }); +}); diff --git a/test/spec/modules/aduptechBidAdapter_spec.js b/test/spec/modules/aduptechBidAdapter_spec.js index 362cd3e506a..bbc4e554f7e 100644 --- a/test/spec/modules/aduptechBidAdapter_spec.js +++ b/test/spec/modules/aduptechBidAdapter_spec.js @@ -77,59 +77,41 @@ describe('AduptechBidAdapter', () => { }); it('should handle empty or missing data', () => { - expect(internal.extractPageUrl(null)).to.equal(utils.getWindowTop().location.href); - expect(internal.extractPageUrl({})).to.equal(utils.getWindowTop().location.href); - expect(internal.extractPageUrl({ refererInfo: {} })).to.equal(utils.getWindowTop().location.href); - expect(internal.extractPageUrl({ refererInfo: { canonicalUrl: null } })).to.equal(utils.getWindowTop().location.href); - expect(internal.extractPageUrl({ refererInfo: { canonicalUrl: '' } })).to.equal(utils.getWindowTop().location.href); + expect(internal.extractPageUrl(null)).to.equal(utils.getWindowSelf().location.href); + expect(internal.extractPageUrl({})).to.equal(utils.getWindowSelf().location.href); + expect(internal.extractPageUrl({ refererInfo: {} })).to.equal(utils.getWindowSelf().location.href); + expect(internal.extractPageUrl({ refererInfo: { canonicalUrl: null } })).to.equal(utils.getWindowSelf().location.href); + expect(internal.extractPageUrl({ refererInfo: { canonicalUrl: '' } })).to.equal(utils.getWindowSelf().location.href); }); - it('should use "pageUrl" from config', () => { - config.setConfig({ pageUrl: 'http://page.url' }); - - expect(internal.extractPageUrl({})).to.equal(config.getConfig('pageUrl')); - }); - - it('should use bidderRequest.refererInfo.canonicalUrl', () => { - const bidderRequest = { - refererInfo: { - canonicalUrl: 'http://canonical.url' - } - }; - - expect(internal.extractPageUrl(bidderRequest)).to.equal(bidderRequest.refererInfo.canonicalUrl); - }); - - it('should prefer bidderRequest.refererInfo.canonicalUrl over "pageUrl" from config', () => { + it('should use bidderRequest.refererInfo.page', () => { const bidderRequest = { refererInfo: { - canonicalUrl: 'http://canonical.url' + page: 'http://canonical.url' } }; - config.setConfig({ pageUrl: 'http://page.url' }); - - expect(internal.extractPageUrl(bidderRequest)).to.equal(bidderRequest.refererInfo.canonicalUrl); + expect(internal.extractPageUrl(bidderRequest)).to.equal(bidderRequest.refererInfo.page); }); }); describe('extractReferrer', () => { it('should handle empty or missing data', () => { - expect(internal.extractReferrer(null)).to.equal(utils.getWindowTop().document.referrer); - expect(internal.extractReferrer({})).to.equal(utils.getWindowTop().document.referrer); - expect(internal.extractReferrer({ refererInfo: {} })).to.equal(utils.getWindowTop().document.referrer); - expect(internal.extractReferrer({ refererInfo: { referer: null } })).to.equal(utils.getWindowTop().document.referrer); - expect(internal.extractReferrer({ refererInfo: { referer: '' } })).to.equal(utils.getWindowTop().document.referrer); + expect(internal.extractReferrer(null)).to.equal(utils.getWindowSelf().document.referrer); + expect(internal.extractReferrer({})).to.equal(utils.getWindowSelf().document.referrer); + expect(internal.extractReferrer({ refererInfo: {} })).to.equal(utils.getWindowSelf().document.referrer); + expect(internal.extractReferrer({ refererInfo: { referer: null } })).to.equal(utils.getWindowSelf().document.referrer); + expect(internal.extractReferrer({ refererInfo: { referer: '' } })).to.equal(utils.getWindowSelf().document.referrer); }); - it('hould use bidderRequest.refererInfo.referer', () => { + it('hould use bidderRequest.refererInfo.ref', () => { const bidderRequest = { refererInfo: { - referer: 'foobar' + ref: 'foobar' } }; - expect(internal.extractReferrer(bidderRequest)).to.equal(bidderRequest.refererInfo.referer); + expect(internal.extractReferrer(bidderRequest)).to.equal(bidderRequest.refererInfo.ref); }); }); @@ -426,8 +408,8 @@ describe('AduptechBidAdapter', () => { const bidderRequest = { auctionId: 'auctionId123', refererInfo: { - canonicalUrl: 'http://crazy.canonical.url', - referer: 'http://crazy.referer.url' + page: 'http://crazy.canonical.url', + ref: 'http://crazy.referer.url' }, gdprConsent: { consentString: 'consentString123', @@ -497,8 +479,8 @@ describe('AduptechBidAdapter', () => { method: ENDPOINT_METHOD, data: { auctionId: bidderRequest.auctionId, - pageUrl: bidderRequest.refererInfo.canonicalUrl, - referrer: bidderRequest.refererInfo.referer, + pageUrl: bidderRequest.refererInfo.page, + referrer: bidderRequest.refererInfo.ref, gdpr: { consentString: bidderRequest.gdprConsent.consentString, consentRequired: bidderRequest.gdprConsent.gdprApplies @@ -526,8 +508,8 @@ describe('AduptechBidAdapter', () => { method: ENDPOINT_METHOD, data: { auctionId: bidderRequest.auctionId, - pageUrl: bidderRequest.refererInfo.canonicalUrl, - referrer: bidderRequest.refererInfo.referer, + pageUrl: bidderRequest.refererInfo.page, + referrer: bidderRequest.refererInfo.ref, gdpr: { consentString: bidderRequest.gdprConsent.consentString, consentRequired: bidderRequest.gdprConsent.gdprApplies @@ -545,6 +527,71 @@ describe('AduptechBidAdapter', () => { } ]); }); + + it('should build a request with floorPrices', () => { + const bidderRequest = { + auctionId: 'auctionId123', + refererInfo: { + page: 'http://crazy.canonical.url', + ref: 'http://crazy.referer.url' + }, + gdprConsent: { + consentString: 'consentString123', + gdprApplies: true + } + }; + + const getFloorResponse = { + currency: 'USD', + floor: '1.23' + }; + + const validBidRequests = [ + { + bidId: 'bidId1', + adUnitCode: 'adUnitCode1', + transactionId: 'transactionId1', + mediaTypes: { + banner: { + sizes: [[100, 200], [300, 400]] + } + }, + params: { + publisher: 'publisher1', + placement: 'placement1' + }, + getFloor: () => { + return getFloorResponse + } + } + ]; + + expect(spec.buildRequests(validBidRequests, bidderRequest)).to.deep.equal([ + { + url: internal.buildEndpointUrl(validBidRequests[0].params.publisher), + method: ENDPOINT_METHOD, + data: { + auctionId: bidderRequest.auctionId, + pageUrl: bidderRequest.refererInfo.page, + referrer: bidderRequest.refererInfo.ref, + gdpr: { + consentString: bidderRequest.gdprConsent.consentString, + consentRequired: bidderRequest.gdprConsent.gdprApplies + }, + imp: [ + { + bidId: validBidRequests[0].bidId, + transactionId: validBidRequests[0].transactionId, + adUnitCode: validBidRequests[0].adUnitCode, + params: validBidRequests[0].params, + banner: validBidRequests[0].mediaTypes.banner, + floorPrice: getFloorResponse.floor + } + ] + } + } + ]); + }); }); describe('interpretResponse', () => { diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 7721295572c..32e9921f0ae 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -52,7 +52,7 @@ describe('Adxcg adapter', function () { adzoneid: '19910113' } }]; - let request = spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}); + let request = spec.buildRequests(validBidRequests, {refererInfo: {page: 'page', domain: 'localhost'}}); assert.equal(request.method, 'POST'); assert.equal(request.url, 'https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); @@ -180,7 +180,7 @@ describe('Adxcg adapter', function () { bidId: 'bidId', params: {adzoneid: '1000'} }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {page: 'page', domain: 'localhost'}}).data); assert.equal(request.device.ua, navigator.userAgent); assert.equal(request.device.w, 100); @@ -190,13 +190,14 @@ describe('Adxcg adapter', function () { it('should send app info', function () { config.setConfig({ app: {id: 'appid'}, - ortb2: {app: {name: 'appname'}} }); + const ortb2 = {app: {name: 'appname'}} let validBidRequests = [{ bidId: 'bidId', - params: {adzoneid: '1000'} + params: {adzoneid: '1000'}, + ortb2 }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}, ortb2}).data); assert.equal(request.app.id, 'appid'); assert.equal(request.app.name, 'appname'); @@ -211,26 +212,28 @@ describe('Adxcg adapter', function () { domain: 'publisher.domain.com' } }, - ortb2: { - site: { - publisher: { - id: 4441, - name: 'publisher\'s name' - } + }); + const ortb2 = { + site: { + publisher: { + id: 4441, + name: 'publisher\'s name' } } - }); + }; + let validBidRequests = [{ bidId: 'bidId', - params: {adzoneid: '1000'} + params: {adzoneid: '1000'}, + ortb2 }]; - let refererInfo = {referer: 'page'}; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo}).data); + let refererInfo = {page: 'page', domain: 'localhost'}; + let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo, ortb2}).data); assert.deepEqual(request.site, { domain: 'localhost', id: '123123', - page: refererInfo.referer, + page: refererInfo.page, publisher: { domain: 'publisher.domain.com', id: 4441, diff --git a/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js b/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js index e4a8fa9dccd..fd698e9e1fd 100644 --- a/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js +++ b/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js @@ -122,7 +122,7 @@ describe('AdxPremium analytics adapter', function () { 'auctionStart': 1589707613899, 'timeout': 2000, 'refererInfo': { - 'referer': 'https://test.com/article/176067', + 'page': 'https://test.com/article/176067', 'reachedTop': true, 'numIframes': 0, 'stack': [ @@ -222,7 +222,7 @@ describe('AdxPremium analytics adapter', function () { 'auctionStart': 1589707613899, 'timeout': 2000, 'refererInfo': { - 'referer': 'https://test.com/article/176067', + 'page': 'https://test.com/article/176067', 'reachedTop': true, 'numIframes': 0, 'stack': [ diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index b5365e8d21a..bab0776a5f4 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -6,6 +6,8 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; describe('Adyoulike Adapter', function () { const canonicalUrl = 'https://canonical.url/?t=%26'; const referrerUrl = 'http://referrer.url/?param=value'; + const pageUrl = 'http://page.url/?param=value'; + const domain = 'domain:123'; const defaultDC = 'hb-api'; const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; const bidderRequest = { @@ -16,7 +18,8 @@ describe('Adyoulike Adapter', function () { consentString: consentString, gdprApplies: true }, - refererInfo: {referer: referrerUrl} + refererInfo: {location: referrerUrl, canonicalUrl, domain}, + ortb2: {site: {page: pageUrl, ref: referrerUrl}} }; const bidRequestWithEmptyPlacement = [ { @@ -592,20 +595,6 @@ describe('Adyoulike Adapter', function () { }); describe('buildRequests', function () { - let canonicalQuery; - - beforeEach(function () { - let canonical = document.createElement('link'); - canonical.rel = 'canonical'; - canonical.href = canonicalUrl; - canonicalQuery = sinon.stub(window.top.document.head, 'querySelector'); - canonicalQuery.withArgs('link[rel="canonical"][href]').returns(canonical); - }); - - afterEach(function () { - canonicalQuery.restore(); - }); - it('Should expand short native image config type', function() { const request = spec.buildRequests(bidRequestWithNativeImageType, bidderRequest); const payload = JSON.parse(request.data); @@ -614,7 +603,8 @@ describe('Adyoulike Adapter', function () { expect(request.method).to.equal('POST'); expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); expect(request.url).to.contains('RefererUrl=' + encodeURIComponent(referrerUrl)); - expect(request.url).to.contains('PublisherDomain=http%3A%2F%2Flocalhost%3A9876'); + expect(request.url).to.contains('PageUrl=' + encodeURIComponent(pageUrl)); + expect(request.url).to.contains('PageReferrer=' + encodeURIComponent(referrerUrl)); expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); @@ -711,17 +701,18 @@ describe('Adyoulike Adapter', function () { expect(request.method).to.equal('POST'); expect(request.url).to.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); expect(request.url).to.contains('RefererUrl=' + encodeURIComponent(referrerUrl)); - expect(request.url).to.contains('PublisherDomain=http%3A%2F%2Flocalhost%3A9876'); + expect(request.url).to.contains('PageUrl=' + encodeURIComponent(pageUrl)); + expect(request.url).to.contains('PageReferrer=' + encodeURIComponent(referrerUrl)); expect(payload.Version).to.equal('1.0'); expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); expect(payload.PageRefreshed).to.equal(false); expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); + expect(payload.ortb2).to.deep.equal({site: {page: pageUrl, ref: referrerUrl}}); }); it('sends bid request to endpoint with single placement without canonical', function () { - canonicalQuery.restore(); - const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); + const request = spec.buildRequests(bidRequestWithSinglePlacement, {...bidderRequest, refererInfo: {...bidderRequest.refererInfo, canonicalUrl: null}}); const payload = JSON.parse(request.data); expect(request.url).to.contain(getEndpoint()); @@ -735,8 +726,7 @@ describe('Adyoulike Adapter', function () { }); it('sends bid request to endpoint with single placement multiple mediatype', function () { - canonicalQuery.restore(); - const request = spec.buildRequests(bidRequestWithMultipleMediatype, bidderRequest); + const request = spec.buildRequests(bidRequestWithSinglePlacement, {...bidderRequest, refererInfo: {...bidderRequest.refererInfo, canonicalUrl: null}}); const payload = JSON.parse(request.data); expect(request.url).to.contain(getEndpoint()); diff --git a/test/spec/modules/airgridRtdProvider_spec.js b/test/spec/modules/airgridRtdProvider_spec.js index a54f6f40e2f..5b1df6f9d4c 100644 --- a/test/spec/modules/airgridRtdProvider_spec.js +++ b/test/spec/modules/airgridRtdProvider_spec.js @@ -110,18 +110,12 @@ describe('airgrid RTD Submodule', function () { .withArgs(agRTD.AG_AUDIENCE_IDS_KEY) .returns(JSON.stringify(MATCHED_AUDIENCES)); const audiences = agRTD.getMatchedAudiencesFromStorage(); - agRTD.setAudiencesUsingBidderOrtb2( - RTD_CONFIG.dataProviders[0], - audiences - ); + const bidderOrtb2 = agRTD.getAudiencesAsBidderOrtb2(RTD_CONFIG.dataProviders[0], audiences); - const allBiddersConfig = config.getBidderConfig(); const bidders = RTD_CONFIG.dataProviders[0].params.bidders; - Object.keys(allBiddersConfig).forEach((bidder) => { + Object.keys(bidderOrtb2).forEach((bidder) => { if (bidders.indexOf(bidder) === -1) return; - expect( - deepAccess(allBiddersConfig[bidder], 'ortb2.user.ext.data.airgrid') - ).to.eql(MATCHED_AUDIENCES); + expect(deepAccess(bidderOrtb2[bidder], 'ortb2.user.ext.data.airgrid')).to.eql(MATCHED_AUDIENCES); }); }); diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index 9bb77520212..a2095d52857 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -50,7 +50,7 @@ describe('AjaAdapter', function () { const bidderRequest = { refererInfo: { - referer: 'https://hoge.com' + page: 'https://hoge.com' } }; @@ -88,7 +88,7 @@ describe('AjaAdapter', function () { const bidderRequest = { refererInfo: { - referer: 'https://hoge.com' + page: 'https://hoge.com' } }; diff --git a/test/spec/modules/akamaiDAPIdSystem_spec.js b/test/spec/modules/akamaiDAPIdSystem_spec.js deleted file mode 100644 index e44285eda34..00000000000 --- a/test/spec/modules/akamaiDAPIdSystem_spec.js +++ /dev/null @@ -1,117 +0,0 @@ -import {akamaiDAPIdSubmodule} from 'modules/akamaiDAPIdSystem.js'; -import * as utils from 'src/utils.js'; -import {server} from 'test/mocks/xhr.js'; -import {getStorageManager} from '../../../src/storageManager.js'; - -export const storage = getStorageManager(); - -const signatureConfigParams = {params: { - apiHostname: 'prebid.dap.akadns.net', - domain: 'prebid.org', - type: 'dap-signature:1.0.0', - apiVersion: 'v1' -}}; - -const tokenizeConfigParams = {params: { - apiHostname: 'prebid.dap.akadns.net', - domain: 'prebid.org', - type: 'email', - identity: 'amishra@xyz.com', - apiVersion: 'v1' -}}; - -const x1TokenizeConfigParams = {params: { - apiHostname: 'prebid.dap.akadns.net', - domain: 'prebid.org', - type: 'email', - identity: 'amishra@xyz.com', - apiVersion: 'x1', - attributes: '{ "cohorts": [ "3:14400", "5:14400", "7:0" ],"first_name": "Ace","last_name": "McCool" }' -}}; - -const consentData = { - gdprApplies: true, - consentString: 'BOkIpDSOkIpDSADABAENCc-AAAApOAFAAMAAsAMIAcAA_g' -}; - -const responseHeader = {'Content-Type': 'application/json'} - -const TEST_ID = '51sd61e3-sd82-4vea-8387-093dffca4a3a'; - -describe('akamaiDAPId getId', function () { - let logErrorStub; - - beforeEach(function () { - logErrorStub = sinon.stub(utils, 'logError'); - }); - - afterEach(function () { - logErrorStub.restore(); - }); - - describe('decode', function () { - it('should respond with an object with dapId containing the value', () => { - expect(akamaiDAPIdSubmodule.decode(TEST_ID)).to.deep.equal({ - dapId: TEST_ID - }); - }); - }); - - describe('getId', function () { - it('should log an error if no configParams were passed when getId', function () { - akamaiDAPIdSubmodule.getId(null); - expect(logErrorStub.calledOnce).to.be.true; - }); - - it('should log an error if configParams were passed without apihostname', function () { - akamaiDAPIdSubmodule.getId({ params: { - domain: 'prebid.org', - type: 'dap-signature:1.0.0' - } }); - expect(logErrorStub.calledOnce).to.be.true; - }); - - it('should log an error if configParams were passed without domain', function () { - akamaiDAPIdSubmodule.getId({ params: { - apiHostname: 'prebid.dap.akadns.net', - type: 'dap-signature:1.0.0' - } }); - expect(logErrorStub.calledOnce).to.be.true; - }); - - it('should log an error if configParams were passed without type', function () { - akamaiDAPIdSubmodule.getId({ params: { - apiHostname: 'prebid.dap.akadns.net', - domain: 'prebid.org' - } }); - expect(logErrorStub.calledOnce).to.be.true; - }); - - it('akamaiDAPId submobile requires consent string to call API', function () { - let consentData = { - gdprApplies: true, - consentString: '' - }; - let submoduleCallback = akamaiDAPIdSubmodule.getId(signatureConfigParams, consentData); - expect(submoduleCallback).to.be.undefined; - }); - - it('should call the signature v1 API and store token in Local storage', function () { - let submoduleCallback1 = akamaiDAPIdSubmodule.getId(signatureConfigParams, consentData).id; - expect(submoduleCallback1).to.be.eq(storage.getDataFromLocalStorage('akamai_dap_token')) - storage.removeDataFromLocalStorage('akamai_dap_token'); - }); - - it('should call the tokenize v1 API and store token in Local storage', function () { - let submoduleCallback = akamaiDAPIdSubmodule.getId(tokenizeConfigParams, consentData).id; - expect(submoduleCallback).to.be.eq(storage.getDataFromLocalStorage('akamai_dap_token')) - storage.removeDataFromLocalStorage('akamai_dap_token'); - }); - - it('should call the tokenize x1 API and store token in Local storage', function () { - let submoduleCallback = akamaiDAPIdSubmodule.getId(x1TokenizeConfigParams, consentData).id; - expect(submoduleCallback).to.be.eq(storage.getDataFromLocalStorage('akamai_dap_token')) - storage.removeDataFromLocalStorage('akamai_dap_token'); - }); - }); -}); diff --git a/test/spec/modules/akamaiDapRtdProvider_spec.js b/test/spec/modules/akamaiDapRtdProvider_spec.js index d7fd3d34099..337fcf57a33 100644 --- a/test/spec/modules/akamaiDapRtdProvider_spec.js +++ b/test/spec/modules/akamaiDapRtdProvider_spec.js @@ -6,6 +6,7 @@ import { storage, DAP_MAX_RETRY_TOKENIZE, DAP_SS_ID, DAP_TOKEN, DAP_MEMBERSHIP, DAP_ENCRYPTED_MEMBERSHIP } from 'modules/akamaiDapRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; +import {hook} from '../../../src/hook.js'; const responseHeader = {'Content-Type': 'application/json'}; describe('akamaiDapRtdProvider', function() { @@ -131,7 +132,15 @@ describe('akamaiDapRtdProvider', function() { } }; + before(() => { + hook.ready(); + }); + + let ortb2, bidConfig; + beforeEach(function() { + bidConfig = {ortb2Fragments: {}}; + ortb2 = bidConfig.ortb2Fragments.global = {}; config.resetConfig(); storage.removeDataFromLocalStorage(DAP_TOKEN); storage.removeDataFromLocalStorage(DAP_MEMBERSHIP); @@ -156,14 +165,13 @@ describe('akamaiDapRtdProvider', function() { let dapGetEncryptedRtdObjStub = sinon.stub(dapUtils, 'dapGetEncryptedRtdObj').returns(cachedEncRtd) let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs') try { - const bidConfig = {}; storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); - expect(config.getConfig().ortb2).to.be.undefined; + expect(ortb2).to.eql({}); generateRealTimeData(bidConfig, () => {}, emoduleConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); + expect(ortb2.user.data).to.deep.include.members([encRtdUserObj]); generateRealTimeData(bidConfig, () => {}, cmoduleConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); + expect(ortb2.user.data).to.deep.include.members([rtdUserObj]); } finally { dapGetRtdObjStub.restore() dapGetMembershipFromLocalStorageStub.restore() @@ -176,11 +184,10 @@ describe('akamaiDapRtdProvider', function() { describe('calling DAP APIs', function() { it('Calls callDapAPIs for unencrypted segments flow', function() { - const bidConfig = {}; storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); let dapExtractExpiryFromTokenStub = sinon.stub(dapUtils, 'dapExtractExpiryFromToken').returns(cacheExpiry) try { - expect(config.getConfig().ortb2).to.be.undefined; + expect(ortb2).to.eql({}); dapUtils.callDapAPIs(bidConfig, () => {}, cmoduleConfig, {}); let membership = {'cohorts': ['9', '11', '13'], 'said': 'sample-said'} let membershipRequest = server.requests[0]; @@ -190,18 +197,17 @@ describe('akamaiDapRtdProvider', function() { responseHeader['Akamai-DAP-Token'] = tokenWithExpiry; tokenizeRequest.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); let data = dapUtils.dapGetRtdObj(membership, cmoduleConfig.params.segtax); - expect(config.getConfig().ortb2.user.data).to.deep.include.members(data.rtd.ortb2.user.data); + expect(ortb2.user.data).to.deep.include.members(data.rtd.ortb2.user.data); } finally { dapExtractExpiryFromTokenStub.restore(); } }); it('Calls callDapAPIs for encrypted segments flow', function() { - const bidConfig = {}; storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); let dapExtractExpiryFromTokenStub = sinon.stub(dapUtils, 'dapExtractExpiryFromToken').returns(cacheExpiry) try { - expect(config.getConfig().ortb2).to.be.undefined; + expect(ortb2).to.eql({}); dapUtils.callDapAPIs(bidConfig, () => {}, emoduleConfig, {}); let encMembership = 'Sample-enc-token'; let membershipRequest = server.requests[0]; @@ -212,7 +218,7 @@ describe('akamaiDapRtdProvider', function() { responseHeader['Akamai-DAP-Token'] = tokenWithExpiry; tokenizeRequest.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); let data = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, emoduleConfig.params.segtax); - expect(config.getConfig().ortb2.user.data).to.deep.include.members(data.rtd.ortb2.user.data); + expect(ortb2.user.data).to.deep.include.members(data.rtd.ortb2.user.data); } finally { dapExtractExpiryFromTokenStub.restore(); } @@ -392,7 +398,7 @@ describe('akamaiDapRtdProvider', function() { domain: 'prebid.org', segtax: 503 }; - expect(dapUtils.dapRefreshMembership(config, 'token', onDone)).to.equal(undefined) + expect(dapUtils.dapRefreshMembership(ortb2, config, 'token', onDone)).to.equal(undefined) const membership = {cohorts: ['1', '5', '7']} expect(dapUtils.dapGetRtdObj(membership, config.segtax)).to.not.equal(undefined); }); @@ -400,11 +406,11 @@ describe('akamaiDapRtdProvider', function() { describe('checkAndAddRealtimeData test', function () { it('add realtime data for segtax 503 and 504', function () { - dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); - dapUtils.checkAndAddRealtimeData(cachedEncRtd, 504); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([encRtdUserObj]); - dapUtils.checkAndAddRealtimeData(cachedRtd, 503); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj]); + dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504); + dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504); + expect(ortb2.user.data).to.deep.include.members([encRtdUserObj]); + dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 503); + expect(ortb2.user.data).to.deep.include.members([rtdUserObj]); }); }); @@ -419,7 +425,7 @@ describe('akamaiDapRtdProvider', function() { describe('dapRefreshToken test', function () { it('test dapRefreshToken success response', function () { - dapUtils.dapRefreshToken(sampleConfig, true, onDone) + dapUtils.dapRefreshToken(ortb2, sampleConfig, true, onDone) let request = server.requests[0]; responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; request.respond(200, responseHeader, JSON.stringify(sampleCachedToken.token)); @@ -427,7 +433,7 @@ describe('akamaiDapRtdProvider', function() { }); it('test dapRefreshToken success response with deviceid 100', function () { - dapUtils.dapRefreshToken(esampleConfig, true, onDone) + dapUtils.dapRefreshToken(ortb2, esampleConfig, true, onDone) let request = server.requests[0]; responseHeader['Akamai-DAP-100'] = sampleCachedToken.token; request.respond(200, responseHeader, ''); @@ -435,7 +441,7 @@ describe('akamaiDapRtdProvider', function() { }); it('test dapRefreshToken success response with exp claim', function () { - dapUtils.dapRefreshToken(sampleConfig, true, onDone) + dapUtils.dapRefreshToken(ortb2, sampleConfig, true, onDone) let request = server.requests[0]; let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' responseHeader['Akamai-DAP-Token'] = tokenWithExpiry; @@ -445,7 +451,7 @@ describe('akamaiDapRtdProvider', function() { it('test dapRefreshToken error response', function () { storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); - dapUtils.dapRefreshToken(sampleConfig, false, onDone) + dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone) let request = server.requests[0]; request.respond(400, responseHeader, 'error'); expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(cacheExpiry);// Since the expiry is same, the token is not updated in the cache @@ -456,35 +462,35 @@ describe('akamaiDapRtdProvider', function() { it('test dapRefreshEncryptedMembership success response', function () { let expiry = Math.round(Date.now() / 1000.0) + 3600; // in seconds let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..f8_At4OqeQXyQcSwThOJ_w.69ImVQ3bEZ6QP7ROCRpAJjNcKY49SEPYR6qTp_8l7L8kQdPbpi4wmuOzt78j7iBrX64k2wltzmQFjDmVKSxDhrEguxpgx6t-L1tT8ZA0UosMWpVsgmKEZxOn2e9ES3jw8RNCS4WSWocSPQX33xSb51evXjm9E1s0tGoLnwXl0GsUvzRsSU86wQG6RZnAQTi7s-r-M2TKibdDjUqgIt62vJ-aBZ7RWw91MINgOdmDNs1bFfbBX5Cy1kd4-kjvRDz_aJ6zHX4sK_7EmQhGEY3tW-A3_l2I88mw-RSJaPkb_IWg0QpVwXDaE2F2g8NpY1PzCRvG_NIE8r28eK5q44OMVitykHmKmBXGDj7z2JVgoXkfo5u0I-dypZARn4GP_7niK932avB-9JD7Mz3TrlU4GZ7IpYfJ91PMsRhrs5xNPQwLZbpuhF76A7Dp7iss71UjkGCiPTU6udfRb4foyf_7xEF66m1eQVcVaMdxEbMuu9GBfdr-d04TbtJhPfUV8JfxTenvRYoi13n0j5kH0M5OgaSQD9kQ3Mrd9u-Cms-BGtT0vf-N8AaFZY_wn0Y4rkpv5HEaH7z3iT4RCHINWrXb_D0WtjLTKQi2YmF8zMlzUOewNJGwZRwbRwxc7JoDIKEc5RZkJYevfJXOEEOPGXZ7AGZxOEsJawPqFqd_nOUosCZS4akHhcDPcVowoecVAV0hhhoS6JEY66PhPp1snbt6yqA-fQhch7z8Y-DZT3Scibvffww3Scg_KFANWp0KeEvHG0vyv9R2F4o66viSS8y21MDnM7Yjk8C-j7aNMldUQbjN_7Yq1nkfe0jiBX_hsINBRPgJHUY4zCaXuyXs-JZZfU92nwG0RT3A_3RP2rpY8-fXp9d3C2QJjEpnmHvTMsuAZCQSBe5DVrJwN_UKedxcJEoOt0wLz6MaCMyYZPd8tnQeqYK1cd3RgQDXtzKC0HDw1En489DqJXEst4eSSkaaW1lImLeaF8XCOaIqPqoyGk4_6KVLw5Q7OnpczuXqYKMd9UTMovGeuTuo1k0ddfEqTq9QwxkwZL51AiDRnwTCAeYBU1krV8FCJQx-mH_WPB5ftZj-o_3pbvANeRk27QBVmjcS-tgDllJkWBxX-4axRXzLw8pUUUZUT_NOL0OiqUCWVm0qMBEpgRQ57Se42-hkLMTzLhhGJOnVcaXU1j4ep-N7faNvbgREBjf_LgzvaWS90a2NJ9bB_J9FyXelhCN_AMLfdOS3fHkeWlZ0u0PMbn5DxXRMe0l9jB-2VJZhcPQRlWoYyoCO3l4F5ZmuQP5Xh9CU4tvSWih6jlwMDgdVWuTpdfPD5bx8ccog3JDq87enx-QtPzLU3gMgouNARJGgNwKS_GJSE1uPrt2oiqgZ3Z0u_I5MKvPdQPV3o-4rsaE730eB4OwAOF-mkGWpzy8Pbl-Qe5PR9mHBhuyJgZ-WDSCHl5yvet2kfO9mPXZlqBQ26fzTcUYH94MULAZn36og6w.3iKGv-Le-AvRmi26W1v6ibRLGbwKbCR92vs-a9t55hw'; - dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + dapUtils.dapRefreshEncryptedMembership(ortb2, esampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; responseHeader['Akamai-DAP-Token'] = encMembership; request.respond(200, responseHeader, encMembership); let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) - expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(expiry); }); it('test dapRefreshEncryptedMembership success response with exp claim', function () { let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQiLCJleHAiOjE2NDM4MzA2NDB9..inYoxwht_aqTIWqGhEm_Gw.wDcCUOCwtqgnNUouaD723gKfm7X7bgkHgtiX4mr07P3tWk25PUQunmwTLhWBB5CYzzGIfIvveG_u4glNRLi_eRSQV4ihKKk1AN-BSSJ3d0CLAdY9I1WG5vX1VmopXyKnV90bl9SLNqnhg4Vxe6YU4ogTYxsKHuIN1EeIH4hpl-HbCQWQ1DQt4mB-MQF8V9AWTfU0D7sFMSK8f9qj6NGmf1__oHdHUlws0t5V2UAn_dhJexsuREK_gh65pczCuly5eEcziZ82LeP-nOhKWSRHB_tS_mKXrRU6_At_EVDgtfA3PSBJ6eQylCii6bTL42vZzz4jZhJv_3eLfRdKqpVT5CWNBzcDoQ2VcQgKgIBtPJ45KFfAYTQ6kdl21QMSjqtu8GTsv1lEZtrqHY6zRiG8_Mu28-PmjEw4LDdZmBDOeroue_MJD6wuE_jlE7J2iVdo8CkVnoRgzFwNbKBo7CK4z0WahV9rhuOm0LKAN5H0jF_gj696U-3fVTDTIb8ndNKNI2_xAhvWs00BFGtUtWgr8QGDGRTDCNGsDgnb_Vva9xCqVOyAE9O3Fq1QYl-tMA-KkBt3zzvmFFpOxpOyH-lUubKLKlsrxKc3GSyVEQ9DDLhrXXJgR5H5BSE4tjlK7p3ODF5qz0FHtIj7oDcgLazFO7z2MuFy2LjJmd3hKl6ujcfYEDiQ4D3pMIo7oiU33aFBD1YpzI4-WzNfJlUt1FoK0-DAXpbbV95s8p08GOD4q81rPw5hRADKJEr0QzrbDwplTWCzT2fKXMg_dIIc5AGqGKnVRUS6UyF1DnHpudNIJWxyWZjWIEw_QNjU0cDFmyPSyKxNrnfq9w8WE2bfbS5KTicxei5QHnC-cnL7Nh7IXp7WOW6R1YHbNPT7Ad4OhnlV-jjrXwkSv4wMAbfwAWoSCchGh7uvENNAeJymuponlJbOgw_GcYM73hMs8Z8W9qxRfbyF4WX5fDKXg61mMlaieHkc0EnoC5q7uKyXuZUehHZ76JLDFmewslLkQq5SkVCttzJePBnY1ouPEHw5ZTzUnG5f01QQOVcjIN-AqXNDbG5IOwq0heyS6vVfq7lZKJdLDVQ21qRjazGPaqYwLzugkWkzCOzPTgyFdbXzgjfmJwylHSOM5Jpnul84GzxEQF-1mHP2A8wtIT-M7_iX24It2wwWvc8qLA6GEqruWCtNyoug8CXo44mKdSSCGeEZHtfMbzXdLIBHCy2jSHz5i8S7DU_R7rE_5Ssrb81CqIYbgsAQBHtOYoyvzduTOruWcci4De0QcULloqImIEHUuIe2lnYO889_LIx5p7nE3UlSvLBo0sPexavFUtHqI6jdG6ye9tdseUEoNBDXW0aWD4D-KXX1JLtAgToPVUtEaXCJI7QavwO9ZG6UZM6jbfuJ5co0fvUXp6qYrFxPQo2dYHkar0nT6s1Zg5l2g8yWlLUJrHdHAzAw_NScUp71OpM4TmNsLnYaPVPcOxMvtJXTanbNWr0VKc8gy9q3k_1XxAnQwiduNs7f5bA-6qCVpayHv5dE7mUhFEwyh1_w95jEaURsQF_hnnd2OqRkADfiok4ZiPU2b38kFW1LXjpI39XXES3JU0e08Rq2uuelyLbCLWuJWq_axuKSZbZvpYeqWtIAde8FjCiO7RPlEc0nyzWBst8RBxQ-Bekg9UXPhxBRcm0HwA.Q2cBSFOQAC-QKDwmjrQXnVQd3jNOppMl9oZfd2yuKeY'; - dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + dapUtils.dapRefreshEncryptedMembership(ortb2, esampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; responseHeader['Akamai-DAP-Token'] = encMembership; request.respond(200, responseHeader, encMembership); let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) - expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(1643830630); }); it('test dapRefreshEncryptedMembership error response', function () { - dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + dapUtils.dapRefreshEncryptedMembership(ortb2, esampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; request.respond(400, responseHeader, 'error'); - expect(config.getConfig().ortb2).to.be.equal(undefined); + expect(ortb2).to.eql({}); }); it('test dapRefreshEncryptedMembership 403 error response', function () { - dapUtils.dapRefreshEncryptedMembership(esampleConfig, sampleCachedToken.token, onDone) + dapUtils.dapRefreshEncryptedMembership(ortb2, esampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; request.respond(403, responseHeader, 'error'); let requestTokenize = server.requests[1]; @@ -499,32 +505,32 @@ describe('akamaiDapRtdProvider', function() { describe('dapRefreshMembership test', function () { it('test dapRefreshMembership success response', function () { let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..17wnrhz6FbWx0Cf6LXpm1A.m9PKVCradk3CZokNKzVHzE06TOqiXYeijgxTQUiQy5Syx-yicnO8DyYX6zQ6rgPcNgUNRt4R4XE5MXuK0laUVQJr9yc9g3vUfQfw69OMYGW_vRlLMPzoNOhF2c4gSyfkRrLr7C0qgALmZO1D11sPflaCTNmO7pmZtRaCOB5buHoWcQhp1bUSJ09DNDb31dX3llimPwjNGSrUhyq_EZl4HopnnjxbM4qVNMY2G_43C_idlVOvbFoTxcDRATd-6MplJoIOIHQLDZEetpIOVcbEYN9gQ_ndBISITwuu5YEgs5C_WPHA25nm6e4BT5R-tawSA8yPyQAupqE8gk4ZWq_2-T0cqyTstIHrMQnZ_vysYN7h6bkzE-KeZRk7GMtySN87_fiu904hLD9QentGegamX6UAbVqQh7Htj7SnMHXkEenjxXAM5mRqQvNCTlw8k-9-VPXs-vTcKLYP8VFf8gMOmuYykgWac1gX-svyAg-24mo8cUbqcsj9relx4Qj5HiXUVyDMBZxK-mHZi-Xz6uv9GlggcsjE13DSszar-j2OetigpdibnJIxRZ-4ew3-vlvZ0Dul3j0LjeWURVBWYWfMjuZ193G7lwR3ohh_NzlNfwOPBK_SYurdAnLh7jJgTW-lVLjH2Dipmi9JwX9s03IQq9opexAn7hlM9oBI6x5asByH8JF8WwZ5GhzDjpDwpSmHPQNGFRSyrx_Sh2CPWNK6C1NJmLkyqAtJ5iw0_al7vPDQyZrKXaLTjBCUnbpJhUZ8dUKtWLzGPjzFXp10muoDIutd1NfyKxk1aWGhx5aerYuLdywv6cT_M8RZTi8924NGj5VA30V5OvEwLLyX93eDhntXZSCbkPHpAfiRZNGXrPY.GhCbWGQz11mIRD4uPKmoAuFXDH7hGnils54zg7N7-TU'} - dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone); + dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(membership)); let rtdObj = dapUtils.dapGetRtdObj(membership, 503); - expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); }); it('test dapRefreshMembership success response with exp claim', function () { let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQ3OTcxNTU4fQ..ptdM5WO-62ypXlKxFXD4FQ.waEo9MHS2NYQCi-zh_p6HgT9BdqGyQbBq4GfGLfsay4nRBgICsTS-VkV6e7xx5U1T8BgpKkRJIZBwTOY5Pkxk9FpK5nnffDSEljRrp1LXLCkNP4qwrlqHInFbZsonNWW4_mW-7aUPlTwIsTbfjTuyHdXHeQa1ALrwFFFWE7QUmPNd2RsHjDwUsxlJPEb5TnHn5W0Mgo_PQZaxvhJInMbxPgtJLoqnJvOqCBEoQY7au7ALZL_nWK8XIwPMF19J7Z3cBg9vQInhr_E3rMdQcAFHEzYfgoNcIYCCR0t1UOqUE3HNtX-E64kZAYKWdlsBb9eW5Gj9hHYyPNL_4Hntjg5eLXGpsocMg0An-qQKGC6hkrxKzeM-GrjpvSaQLNs4iqDpHUtzA02LW_vkLkMNRUiyXVJ3FUZwfyq6uHSRKWZ6UFdAfL0rfJ8q8x8Ll-qJO2Jfyvidlsi9FIs7x1WJrvDCKepfAQM1UXRTonrQljFBAk83PcL2bmWuJDgJZ0lWS4VnZbIf6A7fDourmkDxdVRptvQq5nSjtzCA6whRw0-wGz8ehNJsaJw9H_nG9k4lRKs7A5Lqsyy7TVFrAPjnA_Q1a2H6xF2ULxrtIqoNqdX7k9RjowEZSQlZgZUOAmI4wzjckdcSyC_pUlYBMcBwmlld34mmOJe9EBHAxjdci7Q_9lvj1HTcwGDcQITXnkW9Ux5Jkt9Naw-IGGrnEIADaT2guUAto8W_Gb05TmwHSd6DCmh4zepQCbqeVe6AvPILtVkTgsTTo27Q-NvS7h-XtthJy8425j5kqwxxpZFJ0l0ytc6DUyNCLJXuxi0JFU6-LoSXcROEMVrHa_Achufr9vHIELwacSAIHuwseEvg_OOu1c1WYEwZH8ynBLSjqzy8AnDj24hYgA0YanPAvDqacrYrTUFqURbHmvcQqLBTcYa_gs7uDx4a1EjtP_NvHRlvCgGAaASrjGMhTX8oJxlTqahhQ.pXm-7KqnNK8sbyyczwkVYhcjgiwkpO8LjBBVw4lcyZE'}; - dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone); + dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(membership)); let rtdObj = dapUtils.dapGetRtdObj(membership, 503) - expect(config.getConfig().ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); expect(JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)).expires_at).to.be.equal(1647971548); }); it('test dapRefreshMembership 400 error response', function () { - dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; request.respond(400, responseHeader, 'error'); - expect(config.getConfig().ortb2).to.be.equal(undefined); + expect(ortb2).to.eql({}); }); it('test dapRefreshMembership 403 error response', function () { - dapUtils.dapRefreshMembership(sampleConfig, sampleCachedToken.token, onDone) + dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; request.respond(403, responseHeader, 'error'); expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE); @@ -548,7 +554,7 @@ describe('akamaiDapRtdProvider', function() { describe('Akamai-DAP-SS-ID test', function () { it('Akamai-DAP-SS-ID present in response header', function () { let expiry = Math.round(Date.now() / 1000.0) + 300; // in seconds - dapUtils.dapRefreshToken(sampleConfig, false, onDone) + dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone) let request = server.requests[0]; let sampleSSID = 'Test_SSID_Spec'; responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; @@ -560,7 +566,7 @@ describe('akamaiDapRtdProvider', function() { it('Test if Akamai-DAP-SS-ID is present in request header', function () { let expiry = Math.round(Date.now() / 1000.0) + 100; // in seconds storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify('Test_SSID_Spec')) - dapUtils.dapRefreshToken(sampleConfig, false, onDone) + dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone) let request = server.requests[0]; let ssidHeader = request.requestHeaders['Akamai-DAP-SS-ID']; responseHeader['Akamai-DAP-Token'] = sampleCachedToken.token; diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js index 58a5a3b54ab..1ae9bb56df4 100644 --- a/test/spec/modules/alkimiBidAdapter_spec.js +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -6,6 +6,7 @@ const REQUEST = { 'bidId': '456', 'bidder': 'alkimi', 'sizes': [[300, 250]], + 'adUnitCode': 'bannerAdUnitCode', 'mediaTypes': { 'banner': { 'sizes': [[300, 250]] @@ -15,6 +16,28 @@ const REQUEST = { bidFloor: 0.1, token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7 + }, + 'userIdAsEids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'test', + 'atype': 1 + }] + }, { + 'source': 'pubcid.org', + 'uids': [{ + 'id': 'test', + 'atype': 1 + }] + }], + 'schain': { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'alkimi-onboarding.com', + sid: '00001', + hp: 1 + }] } } @@ -88,15 +111,35 @@ describe('alkimiBidAdapter', function () { const bidderRequest = spec.buildRequests(bidRequests, { auctionId: '123', refererInfo: { - referer: 'http://test.com/path.html' - } + page: 'http://test.com/path.html' + }, + gdprConsent: { + consentString: 'test-consent', + vendorData: {}, + gdprApplies: true + }, + uspConsent: 'uspConsent' + }) + + it('should return a properly formatted request with eids defined', function () { + expect(bidderRequest.data.eids).to.deep.equal(REQUEST.userIdAsEids) + }) + + it('should return a properly formatted request with gdpr defined', function () { + expect(bidderRequest.data.gdprConsent.consentRequired).to.equal(true) + expect(bidderRequest.data.gdprConsent.consentString).to.equal('test-consent') + }) + + it('should return a properly formatted request with uspConsent defined', function () { + expect(bidderRequest.data.uspConsent).to.equal('uspConsent') }) it('sends bid request to ENDPOINT via POST', function () { expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.equal('123') expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') - expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, width: 300, height: 250, impMediaType: 'Banner' }) + expect(bidderRequest.data.schain).to.deep.contains({ver: '1.0', complete: 1, nodes: [{asi: 'alkimi-onboarding.com', sid: '00001', hp: 1}]}) + expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, width: 300, height: 250, impMediaType: 'Banner', adUnitCode: 'bannerAdUnitCode' }) expect(bidderRequest.data.signRequest.randomUUID).to.equal(undefined) expect(bidderRequest.data.bidIds).to.deep.contains('456') expect(bidderRequest.data.signature).to.equal(undefined) diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index f502d631c17..e6a87d6035e 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -31,15 +31,6 @@ const sampleFPD = { } }; -const stubConfig = (withStub) => { - const stub = sinon.stub(config, 'getConfig').callsFake( - (arg) => arg === 'ortb2' ? sampleFPD : null - ) - - withStub(); - stub.restore(); -}; - const sampleBidderRequest = { gdprConsent: { gdprApplies: true, @@ -49,9 +40,11 @@ const sampleBidderRequest = { auctionId: utils.getUniqueIdentifierStr(), uspConsent: '1YYY', refererInfo: { - referer: 'https://www.prebid.org', + location: 'https://www.prebid.org', + topmostLocation: 'https://www.prebid.org', canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' - } + }, + ortb2: sampleFPD }; const sampleBidRequestBase = { @@ -235,35 +228,22 @@ describe('AmxBidAdapter', () => { const { data } = spec.buildRequests([sampleBidRequestBase], { ...sampleBidderRequest, refererInfo: { - numIframes: 1, - referer: 'http://search-traffic-source.com', - stack: [] + location: null, + topmostLocation: null, + ref: 'http://search-traffic-source.com', } }); expect(data.do).to.equal('localhost') expect(data.re).to.equal('http://search-traffic-source.com'); }); - it('if we are in AMP, make sure we use the canonical URL or the referrer (which is sourceUrl)', () => { - const { data } = spec.buildRequests([sampleBidRequestBase], { - ...sampleBidderRequest, - refererInfo: { - isAmp: true, - referer: 'http://real-publisher-site.com/content', - stack: [] - } - }); - expect(data.do).to.equal('real-publisher-site.com') - expect(data.re).to.equal('http://real-publisher-site.com/content'); - }) - it('if prebid is in an iframe, will use the topmost url as domain', () => { const { data } = spec.buildRequests([sampleBidRequestBase], { ...sampleBidderRequest, refererInfo: { - numIframes: 1, - referer: 'http://search-traffic-source.com', - stack: ['http://top-site.com', 'http://iframe.com'] + location: null, + topmostLocation: 'http://top-site.com', + ref: 'http://search-traffic-source.com', } }); expect(data.do).to.equal('top-site.com'); @@ -293,10 +273,8 @@ describe('AmxBidAdapter', () => { expect(data.trc).to.equal(0) }); it('will forward first-party data', () => { - stubConfig(() => { - const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); - expect(data.fpd2).to.deep.equal(sampleFPD) - }); + const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); + expect(data.fpd2).to.deep.equal(sampleFPD) }); it('will collect & forward RTI user IDs', () => { diff --git a/test/spec/modules/andBeyondMediaBidAdapter_spec.js b/test/spec/modules/andBeyondMediaBidAdapter_spec.js new file mode 100644 index 00000000000..b739bec288e --- /dev/null +++ b/test/spec/modules/andBeyondMediaBidAdapter_spec.js @@ -0,0 +1,400 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/andBeyondMediaBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'beyondmedia' + +describe('AndBeyondMediaBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + expect(spec.isBidRequestValid(bids[1])).to.be.true; + expect(spec.isBidRequestValid(bids[2])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://backend.andbeyond.media/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cookies.andbeyond.media/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cookies.andbeyond.media/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index ff1d3b813ce..773c9925d58 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -730,22 +730,4 @@ describe('ApacdexBidAdapter', function () { expect(validateGeoObject(geoObject)).to.equal(false); }); }); - - describe('getDomain', function () { - it('should return valid domain from publisherDomain config', () => { - let pageUrl = 'https://www.example.com/page/prebid/exam.html'; - config.setConfig({ publisherDomain: pageUrl }); - expect(getDomain(pageUrl)).to.equal('example.com'); - }); - it('should return valid domain from pageUrl argument', () => { - let pageUrl = 'https://www.example.com/page/prebid/exam.html'; - config.setConfig({ publisherDomain: '' }); - expect(getDomain(pageUrl)).to.equal('example.com'); - }); - it('should return undefined if pageUrl and publisherDomain not config', () => { - let pageUrl; - config.setConfig({ publisherDomain: '' }); - expect(getDomain(pageUrl)).to.equal(pageUrl); - }); - }); }); diff --git a/test/spec/modules/appierBidAdapter_spec.js b/test/spec/modules/appierBidAdapter_spec.js index 5b6ccf14162..8b6ad5c2f6f 100644 --- a/test/spec/modules/appierBidAdapter_spec.js +++ b/test/spec/modules/appierBidAdapter_spec.js @@ -64,12 +64,16 @@ describe('AppierAdapter', function () { 'auctionId': '1d1a030790a475', }; const fakeBidRequests = [bid]; - const fakeBidderRequest = {refererInfo: { - 'referer': 'fakeReferer', - 'reachedTop': true, - 'numIframes': 1, - 'stack': [] - }}; + const fakeBidderRequest = { + refererInfo: { + legacy: { + 'referer': 'fakeReferer', + 'reachedTop': true, + 'numIframes': 1, + 'stack': [] + } + } + }; const builtRequests = spec.buildRequests(fakeBidRequests, fakeBidderRequest); expect(builtRequests.length).to.equal(1); @@ -77,7 +81,7 @@ describe('AppierAdapter', function () { expect(builtRequests[0].url).match(/v1\/prebid\/bid/); expect(builtRequests[0].data).deep.equal({ 'bids': fakeBidRequests, - 'refererInfo': fakeBidderRequest.refererInfo, + 'refererInfo': fakeBidderRequest.refererInfo.legacy, 'version': ADAPTER_VERSION }); }); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 6c72501be9c..211c0d64e67 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -72,13 +72,13 @@ describe('AppNexusAdapter', function () { } ]; - beforeEach(function() { - getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { + beforeEach(function () { + getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function () { return []; }); }); - afterEach(function() { + afterEach(function () { getAdUnitsStub.restore(); }); @@ -97,10 +97,10 @@ describe('AppNexusAdapter', function () { const payload = JSON.parse(request.data); expect(payload.tags[0].private_sizes).to.exist; - expect(payload.tags[0].private_sizes).to.deep.equal([{width: 300, height: 250}]); + expect(payload.tags[0].private_sizes).to.deep.equal([{ width: 300, height: 250 }]); }); - it('should add position in request', function() { + it('should add position in request', function () { // set from bid.params let bidRequest = deepClone(bidRequests[0]); bidRequest.params.position = 'above'; @@ -149,7 +149,7 @@ describe('AppNexusAdapter', function () { expect(payload4.tags[0].position).to.deep.equal(1); }); - it('should add publisher_id in request', function() { + it('should add publisher_id in request', function () { let bidRequest = Object.assign({}, bidRequests[0], { @@ -194,8 +194,13 @@ describe('AppNexusAdapter', function () { transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' }]; - ['banner', 'video', 'native'].forEach(type => { - getAdUnitsStub.callsFake(function(...args) { + let types = ['banner', 'video']; + if (FEATURES.NATIVE) { + types.push('native'); + } + + types.forEach(type => { + getAdUnitsStub.callsFake(function (...args) { return adUnits; }); @@ -214,7 +219,7 @@ describe('AppNexusAdapter', function () { }); }); - it('should not populate the ad_types array when adUnit.mediaTypes is undefined', function() { + it('should not populate the ad_types array when adUnit.mediaTypes is undefined', function () { const bidRequest = Object.assign({}, bidRequests[0]); const request = spec.buildRequests([bidRequest]); const payload = JSON.parse(request.data); @@ -225,7 +230,7 @@ describe('AppNexusAdapter', function () { it('should populate the ad_types array on outstream requests', function () { const bidRequest = Object.assign({}, bidRequests[0]); bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = {context: 'outstream'}; + bidRequest.mediaTypes.video = { context: 'outstream' }; const request = spec.buildRequests([bidRequest]); const payload = JSON.parse(request.data); @@ -264,7 +269,7 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].hb_source).to.deep.equal(1); }); - it('should include ORTB video values when video params were not set', function() { + it('should include ORTB video values when video params were not set', function () { let bidRequest = deepClone(bidRequests[0]); bidRequest.params = { placementId: '1234235', @@ -319,7 +324,7 @@ describe('AppNexusAdapter', function () { bidRequest1 = Object.assign({}, bidRequest1, videoData, { renderer: { url: 'https://test.renderer.url', - render: function () {} + render: function () { } } }); @@ -361,7 +366,7 @@ describe('AppNexusAdapter', function () { expect(payload.user).to.exist; expect(payload.user).to.deep.equal({ external_uid: '123', - segments: [{id: 123}, {id: 987, value: 876}] + segments: [{ id: 123 }, { id: 987, value: 876 }] }); }); @@ -395,7 +400,7 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].reserve).to.exist.and.to.equal(3); }); - it('should duplicate adpod placements into batches and set correct maxduration', function() { + it('should duplicate adpod placements into batches and set correct maxduration', function () { let bidRequest = Object.assign({}, bidRequests[0], { @@ -428,7 +433,7 @@ describe('AppNexusAdapter', function () { expect(payload2.tags[0].video.maxduration).to.equal(30); }); - it('should round down adpod placements when numbers are uneven', function() { + it('should round down adpod placements when numbers are uneven', function () { let bidRequest = Object.assign({}, bidRequests[0], { @@ -451,7 +456,7 @@ describe('AppNexusAdapter', function () { expect(payload.tags.length).to.equal(2); }); - it('should duplicate adpod placements when requireExactDuration is set', function() { + it('should duplicate adpod placements when requireExactDuration is set', function () { let bidRequest = Object.assign({}, bidRequests[0], { @@ -493,7 +498,7 @@ describe('AppNexusAdapter', function () { expect(payload2tagsWith30.length).to.equal(5); }); - it('should set durations for placements when requireExactDuration is set and numbers are uneven', function() { + it('should set durations for placements when requireExactDuration is set and numbers are uneven', function () { let bidRequest = Object.assign({}, bidRequests[0], { @@ -524,7 +529,7 @@ describe('AppNexusAdapter', function () { expect(tagsWith60.length).to.equal(1); }); - it('should break adpod request into batches', function() { + it('should break adpod request into batches', function () { let bidRequest = Object.assign({}, bidRequests[0], { @@ -552,7 +557,7 @@ describe('AppNexusAdapter', function () { expect(payload3.tags.length).to.equal(15); }); - it('should contain hb_source value for adpod', function() { + it('should contain hb_source value for adpod', function () { let bidRequest = Object.assign({}, bidRequests[0], { @@ -574,7 +579,7 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].hb_source).to.deep.equal(7); }); - it('should contain hb_source value for other media', function() { + it('should contain hb_source value for other media', function () { let bidRequest = Object.assign({}, bidRequests[0], { @@ -590,7 +595,7 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].hb_source).to.deep.equal(1); }); - it('adds brand_category_exclusion to request when set', function() { + it('adds brand_category_exclusion to request when set', function () { let bidRequest = Object.assign({}, bidRequests[0]); sinon .stub(config, 'getConfig') @@ -605,7 +610,7 @@ describe('AppNexusAdapter', function () { config.getConfig.restore(); }); - it('adds auction level keywords to request when set', function() { + it('adds auction level keywords and ortb2 keywords to request when set', function () { let bidRequest = Object.assign({}, bidRequests[0]); sinon .stub(config, 'getConfig') @@ -613,10 +618,31 @@ describe('AppNexusAdapter', function () { .returns({ gender: 'm', music: ['rock', 'pop'], - test: '' + test: '', + tools: 'power' }); - const request = spec.buildRequests([bidRequest]); + const bidderRequest = { + ortb2: { + site: { + keywords: 'power tools, drills, tools=industrial', + content: { + keywords: 'video, source=streaming' + } + }, + user: { + keywords: 'tools=home,renting' + }, + app: { + keywords: 'app=iphone 11', + content: { + keywords: 'appcontent=home repair, dyi' + } + } + } + }; + + const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); expect(payload.keywords).to.deep.equal([{ @@ -627,87 +653,111 @@ describe('AppNexusAdapter', function () { 'value': ['rock', 'pop'] }, { 'key': 'test' + }, { + 'key': 'tools', + 'value': ['power', 'industrial', 'home'] + }, { + 'key': 'power tools' + }, { + 'key': 'drills' + }, { + 'key': 'video' + }, { + 'key': 'source', + 'value': ['streaming'] + }, { + 'key': 'renting' + }, { + 'key': 'app', + 'value': ['iphone 11'] + }, { + 'key': 'appcontent', + 'value': ['home repair'] + }, { + 'key': 'dyi' }]); config.getConfig.restore(); }); - it('should attach native params to the request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'native', - nativeParams: { - title: {required: true}, - body: {required: true}, - body2: {required: true}, - image: {required: true, sizes: [100, 100]}, - icon: {required: true}, - cta: {required: false}, - rating: {required: true}, - sponsoredBy: {required: true}, - privacyLink: {required: true}, - displayUrl: {required: true}, - address: {required: true}, - downloads: {required: true}, - likes: {required: true}, - phone: {required: true}, - price: {required: true}, - salePrice: {required: true} + if (FEATURES.NATIVE) { + it('should attach native params to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + title: { required: true }, + body: { required: true }, + body2: { required: true }, + image: { required: true, sizes: [100, 100] }, + icon: { required: true }, + cta: { required: false }, + rating: { required: true }, + sponsoredBy: { required: true }, + privacyLink: { required: true }, + displayUrl: { required: true }, + address: { required: true }, + downloads: { required: true }, + likes: { required: true }, + phone: { required: true }, + price: { required: true }, + salePrice: { required: true } + } } - } - ); + ); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); - expect(payload.tags[0].native.layouts[0]).to.deep.equal({ - title: {required: true}, - description: {required: true}, - desc2: {required: true}, - main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, - icon: {required: true}, - ctatext: {required: false}, - rating: {required: true}, - sponsored_by: {required: true}, - privacy_link: {required: true}, - displayurl: {required: true}, - address: {required: true}, - downloads: {required: true}, - likes: {required: true}, - phone: {required: true}, - price: {required: true}, - saleprice: {required: true}, - privacy_supported: true + expect(payload.tags[0].native.layouts[0]).to.deep.equal({ + title: { required: true }, + description: { required: true }, + desc2: { required: true }, + main_image: { required: true, sizes: [{ width: 100, height: 100 }] }, + icon: { required: true }, + ctatext: { required: false }, + rating: { required: true }, + sponsored_by: { required: true }, + privacy_link: { required: true }, + displayurl: { required: true }, + address: { required: true }, + downloads: { required: true }, + likes: { required: true }, + phone: { required: true }, + price: { required: true }, + saleprice: { required: true }, + privacy_supported: true + }); + expect(payload.tags[0].hb_source).to.equal(1); }); - expect(payload.tags[0].hb_source).to.equal(1); - }); - it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'native', - nativeParams: { - image: { required: true } + it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + image: { required: true } + } } - } - ); - bidRequest.sizes = [[150, 100], [300, 250]]; + ); + bidRequest.sizes = [[150, 100], [300, 250]]; - let request = spec.buildRequests([bidRequest]); - let payload = JSON.parse(request.data); - expect(payload.tags[0].sizes).to.deep.equal([{width: 150, height: 100}, {width: 300, height: 250}]); + let request = spec.buildRequests([bidRequest]); + let payload = JSON.parse(request.data); + expect(payload.tags[0].sizes).to.deep.equal([{ width: 150, height: 100 }, { width: 300, height: 250 }]); - delete bidRequest.sizes; + delete bidRequest.sizes; - request = spec.buildRequests([bidRequest]); - payload = JSON.parse(request.data); + request = spec.buildRequests([bidRequest]); + payload = JSON.parse(request.data); - expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); - }); + expect(payload.tags[0].sizes).to.deep.equal([{ width: 1, height: 1 }]); + }); + } - it('should convert keyword params to proper form and attaches to request', function () { + it('should convert keyword params and adUnit ortb2 keywords to proper form and attaches to request', function () { let bidRequest = Object.assign({}, bidRequests[0], { @@ -721,7 +771,14 @@ describe('AppNexusAdapter', function () { singleValNum: 123, emptyStr: '', emptyArr: [''], - badValue: {'foo': 'bar'} // should be dropped + badValue: { 'foo': 'bar' } // should be dropped + } + }, + ortb2Imp: { + ext: { + data: { + keywords: 'ortb2=yes,ortb2test, multiValMixed=4, singleValNum=456' + } } } } @@ -741,14 +798,19 @@ describe('AppNexusAdapter', function () { 'value': ['5'] }, { 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] + 'value': ['value1', '2', 'value3', '4'] }, { 'key': 'singleValNum', - 'value': ['123'] + 'value': ['123', '456'] }, { 'key': 'emptyStr' }, { 'key': 'emptyArr' + }, { + 'key': 'ortb2', + 'value': ['yes'] + }, { + 'key': 'ortb2test' }]); }); @@ -796,7 +858,7 @@ describe('AppNexusAdapter', function () { bidderRequest.bids = bidRequests; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.options).to.deep.equal({withCredentials: true}); + expect(request.options).to.deep.equal({ withCredentials: true }); const payload = JSON.parse(request.data); expect(payload.gdpr_consent).to.exist; @@ -805,7 +867,7 @@ describe('AppNexusAdapter', function () { expect(payload.gdpr_consent.addtl_consent).to.exist.and.to.deep.equal([7, 12, 35, 62, 66, 70, 89, 93, 108]); }); - it('should add us privacy string to payload', function() { + it('should add us privacy string to payload', function () { let consentString = '1YA-'; let bidderRequest = { 'bidderCode': 'appnexus', @@ -871,7 +933,7 @@ describe('AppNexusAdapter', function () { const bidRequest = Object.assign({}, bidRequests[0]); const bidderRequest = { refererInfo: { - referer: 'https://example.com/page.html', + topmostLocation: 'https://example.com/page.html', reachedTop: true, numIframes: 2, stack: [ @@ -895,14 +957,11 @@ describe('AppNexusAdapter', function () { it('if defined, should include publisher pageUrl to normal referer info in payload', function () { const bidRequest = Object.assign({}, bidRequests[0]); - sinon - .stub(config, 'getConfig') - .withArgs('pageUrl') - .returns('https://mypub.override.com/test/page.html'); const bidderRequest = { refererInfo: { - referer: 'https://example.com/page.html', + canonicalUrl: 'https://mypub.override.com/test/page.html', + topmostLocation: 'https://example.com/page.html', reachedTop: true, numIframes: 2, stack: [ @@ -923,8 +982,6 @@ describe('AppNexusAdapter', function () { rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(','), rd_can: 'https://mypub.override.com/test/page.html' }); - - config.getConfig.restore(); }); it('should populate schain if available', function () { @@ -978,7 +1035,7 @@ describe('AppNexusAdapter', function () { .returns(true); const request = spec.buildRequests([bidRequest]); - expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); + expect(request.options.customHeaders).to.deep.equal({ 'X-Is-Test': 1 }); config.getConfig.restore(); }); @@ -1023,10 +1080,6 @@ describe('AppNexusAdapter', function () { criteoId: 'sample-criteo-userid', netId: 'sample-netId-userid', idl_env: 'sample-idl-userid', - flocId: { - id: 'sample-flocid-value', - version: 'chrome.1.0' - }, pubProvidedId: [{ source: 'puburl.com', uids: [{ @@ -1058,11 +1111,6 @@ describe('AppNexusAdapter', function () { id: 'sample-criteo-userid', }); - expect(payload.eids).to.deep.include({ - source: 'chrome.com', - id: 'sample-flocid-value' - }); - expect(payload.eids).to.deep.include({ source: 'netid.de', id: 'sample-netId-userid', @@ -1141,12 +1189,12 @@ describe('AppNexusAdapter', function () { let bfStub; let bidderSettingsStorage; - before(function() { + before(function () { bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); bidderSettingsStorage = $$PREBID_GLOBAL$$.bidderSettings; }); - after(function() { + after(function () { bfStub.restore(); $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsStorage; }); @@ -1202,6 +1250,7 @@ describe('AppNexusAdapter', function () { it('should get correct bid response', function () { let expectedResponse = [ { + 'adId': '3a1f23123e', 'requestId': '3db3773286ee59', 'cpm': 0.5, 'creativeId': 29681110, @@ -1234,7 +1283,7 @@ describe('AppNexusAdapter', function () { adUnitCode: 'code' }] }; - let result = spec.interpretResponse({ body: response }, {bidderRequest}); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); @@ -1285,7 +1334,7 @@ describe('AppNexusAdapter', function () { }; let bidderRequest; - let result = spec.interpretResponse({ body: response }, {bidderRequest}); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); expect(result.length).to.equal(0); }); @@ -1318,7 +1367,7 @@ describe('AppNexusAdapter', function () { }] } - let result = spec.interpretResponse({ body: response }, {bidderRequest}); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); expect(result[0]).to.have.property('vastXml'); expect(result[0]).to.have.property('vastImpUrl'); expect(result[0]).to.have.property('mediaType', 'video'); @@ -1353,7 +1402,7 @@ describe('AppNexusAdapter', function () { }] } - let result = spec.interpretResponse({ body: response }, {bidderRequest}); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); expect(result[0]).to.have.property('vastUrl'); expect(result[0]).to.have.property('vastImpUrl'); expect(result[0]).to.have.property('mediaType', 'video'); @@ -1394,61 +1443,63 @@ describe('AppNexusAdapter', function () { }; bfStub.returns('1'); - let result = spec.interpretResponse({ body: response }, {bidderRequest}); + let result = spec.interpretResponse({ body: response }, { bidderRequest }); expect(result[0]).to.have.property('vastUrl'); expect(result[0].video.context).to.equal('adpod'); expect(result[0].video.durationSeconds).to.equal(30); }); - it('handles native responses', function () { - let response1 = deepClone(response); - response1.tags[0].ads[0].ad_type = 'native'; - response1.tags[0].ads[0].rtb.native = { - 'title': 'Native Creative', - 'desc': 'Cool description great stuff', - 'desc2': 'Additional body text', - 'ctatext': 'Do it', - 'sponsored': 'AppNexus', - 'icon': { - 'width': 0, - 'height': 0, - 'url': 'https://cdn.adnxs.com/icon.png' - }, - 'main_img': { - 'width': 2352, - 'height': 1516, - 'url': 'https://cdn.adnxs.com/img.png' - }, - 'link': { - 'url': 'https://www.appnexus.com', - 'fallback_url': '', - 'click_trackers': ['https://nym1-ib.adnxs.com/click'] - }, - 'impression_trackers': ['https://example.com'], - 'rating': '5', - 'displayurl': 'https://AppNexus.com/?url=display_url', - 'likes': '38908320', - 'downloads': '874983', - 'price': '9.99', - 'saleprice': 'FREE', - 'phone': '1234567890', - 'address': '28 W 23rd St, New York, NY 10010', - 'privacy_link': 'https://appnexus.com/?url=privacy_url', - 'javascriptTrackers': '' - }; - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } + if (FEATURES.NATIVE) { + it('handles native responses', function () { + let response1 = deepClone(response); + response1.tags[0].ads[0].ad_type = 'native'; + response1.tags[0].ads[0].rtb.native = { + 'title': 'Native Creative', + 'desc': 'Cool description great stuff', + 'desc2': 'Additional body text', + 'ctatext': 'Do it', + 'sponsored': 'AppNexus', + 'icon': { + 'width': 0, + 'height': 0, + 'url': 'https://cdn.adnxs.com/icon.png' + }, + 'main_img': { + 'width': 2352, + 'height': 1516, + 'url': 'https://cdn.adnxs.com/img.png' + }, + 'link': { + 'url': 'https://www.appnexus.com', + 'fallback_url': '', + 'click_trackers': ['https://nym1-ib.adnxs.com/click'] + }, + 'impression_trackers': ['https://example.com'], + 'rating': '5', + 'displayurl': 'https://AppNexus.com/?url=display_url', + 'likes': '38908320', + 'downloads': '874983', + 'price': '9.99', + 'saleprice': 'FREE', + 'phone': '1234567890', + 'address': '28 W 23rd St, New York, NY 10010', + 'privacy_link': 'https://appnexus.com/?url=privacy_url', + 'javascriptTrackers': '' + }; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } - let result = spec.interpretResponse({ body: response1 }, {bidderRequest}); - expect(result[0].native.title).to.equal('Native Creative'); - expect(result[0].native.body).to.equal('Cool description great stuff'); - expect(result[0].native.cta).to.equal('Do it'); - expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); - }); + let result = spec.interpretResponse({ body: response1 }, { bidderRequest }); + expect(result[0].native.title).to.equal('Native Creative'); + expect(result[0].native.body).to.equal('Cool description great stuff'); + expect(result[0].native.cta).to.equal('Do it'); + expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); + }); + } it('supports configuring outstream renderers', function () { const outstreamResponse = deepClone(response); @@ -1471,13 +1522,13 @@ describe('AppNexusAdapter', function () { }] }; - const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); + const result = spec.interpretResponse({ body: outstreamResponse }, { bidderRequest }); expect(result[0].renderer.config).to.deep.equal( bidderRequest.bids[0].renderer.options ); }); - it('should add deal_priority and deal_code', function() { + it('should add deal_priority and deal_code', function () { let responseWithDeal = deepClone(response); responseWithDeal.tags[0].ads[0].ad_type = 'video'; responseWithDeal.tags[0].ads[0].deal_priority = 5; @@ -1499,12 +1550,12 @@ describe('AppNexusAdapter', function () { } }] } - let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); + let result = spec.interpretResponse({ body: responseWithDeal }, { bidderRequest }); expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); expect(result[0].video.dealTier).to.equal(5); }); - it('should add advertiser id', function() { + it('should add advertiser id', function () { let responseAdvertiserId = deepClone(response); responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; @@ -1514,11 +1565,11 @@ describe('AppNexusAdapter', function () { adUnitCode: 'code' }] } - let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + let result = spec.interpretResponse({ body: responseAdvertiserId }, { bidderRequest }); expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); }); - it('should add brand id', function() { + it('should add brand id', function () { let responseBrandId = deepClone(response); responseBrandId.tags[0].ads[0].brand_id = 123; @@ -1528,11 +1579,11 @@ describe('AppNexusAdapter', function () { adUnitCode: 'code' }] } - let result = spec.interpretResponse({ body: responseBrandId }, {bidderRequest}); + let result = spec.interpretResponse({ body: responseBrandId }, { bidderRequest }); expect(Object.keys(result[0].meta)).to.include.members(['brandId']); }); - it('should add advertiserDomains', function() { + it('should add advertiserDomains', function () { let responseAdvertiserId = deepClone(response); responseAdvertiserId.tags[0].ads[0].adomain = ['123']; @@ -1542,7 +1593,7 @@ describe('AppNexusAdapter', function () { adUnitCode: 'code' }] } - let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + let result = spec.interpretResponse({ body: responseAdvertiserId }, { bidderRequest }); expect(Object.keys(result[0].meta)).to.include.members(['advertiserDomains']); expect(Object.keys(result[0].meta.advertiserDomains)).to.deep.equal([]); }); @@ -1552,11 +1603,11 @@ describe('AppNexusAdapter', function () { let gcStub; let adUnit = { bids: [{ bidder: 'appnexus' }] }; ; - before(function() { + before(function () { gcStub = sinon.stub(config, 'getConfig'); }); - after(function() { + after(function () { gcStub.restore(); }); diff --git a/test/spec/modules/apstreamBidAdapter_spec.js b/test/spec/modules/apstreamBidAdapter_spec.js index e640c009989..3efb5fd38d5 100644 --- a/test/spec/modules/apstreamBidAdapter_spec.js +++ b/test/spec/modules/apstreamBidAdapter_spec.js @@ -33,6 +33,11 @@ describe('AP Stream adapter', function() { let mockConfig; beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + apstream: { + storageAllowed: true + } + }; mockConfig = { apstream: { publisherId: '4321' @@ -44,6 +49,7 @@ describe('AP Stream adapter', function() { }); afterEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; config.getConfig.restore(); }); @@ -194,29 +200,6 @@ describe('AP Stream adapter', function() { describe('dsu', function() { it('should pass DSU from local storage if set', function() { - let dsu = 'some_dsu'; - localStorage.setItem('apr_dsu', dsu); - - const bidderRequest = { - gdprConsent: { - gdprApplies: true, - consentString: 'consentDataString', - vendorData: { - vendorConsents: { - '394': true - } - } - } - }; - - const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - - assert.equal(request.dsu, dsu); - }); - - it('should generate new DSU if nothing in local storage', function() { - localStorage.removeItem('apr_dsu'); - const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -230,10 +213,7 @@ describe('AP Stream adapter', function() { }; const request = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - let dsu = localStorage.getItem('apr_dsu'); - - assert.isNotEmpty(dsu); - assert.equal(request.dsu, dsu); + assert.isNotEmpty(request.dsu); }); }); }); diff --git a/test/spec/modules/asoBidAdapter_spec.js b/test/spec/modules/asoBidAdapter_spec.js index 0dc779a300d..5ac44cb1db4 100644 --- a/test/spec/modules/asoBidAdapter_spec.js +++ b/test/spec/modules/asoBidAdapter_spec.js @@ -62,7 +62,8 @@ describe('Adserver.Online bidding adapter', function () { refererInfo: { numIframes: 0, reachedTop: true, - referer: 'https://example.com' + page: 'https://example.com', + domain: 'example.com' } }; diff --git a/test/spec/modules/audiencerunBidAdapter_spec.js b/test/spec/modules/audiencerunBidAdapter_spec.js index 7f1e059fa2a..57de8bdb0df 100644 --- a/test/spec/modules/audiencerunBidAdapter_spec.js +++ b/test/spec/modules/audiencerunBidAdapter_spec.js @@ -114,7 +114,8 @@ describe('AudienceRun bid adapter tests', function () { }, refererInfo: { canonicalUrl: undefined, - referer: 'https://example.com', + page: 'https://example.com', + topmostLocation: 'https://example.com', numIframes: 0, reachedTop: true, }, diff --git a/test/spec/modules/axonixBidAdapter_spec.js b/test/spec/modules/axonixBidAdapter_spec.js index a952d527600..37f409e5769 100644 --- a/test/spec/modules/axonixBidAdapter_spec.js +++ b/test/spec/modules/axonixBidAdapter_spec.js @@ -65,7 +65,7 @@ describe('AxonixBidAdapter', function () { gdprApplies: true }, refererInfo: { - referer: 'https://www.prebid.org', + page: 'https://www.prebid.org', canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' } }; diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index d9b8cac10b4..addd4304c7d 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -129,7 +129,7 @@ describe('BeachfrontAdapter', function () { it('should attach the bid request object', function () { bidRequests[0].mediaTypes = { video: {} }; bidRequests[1].mediaTypes = { video: {} }; - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, {}); expect(requests[0].bidRequest).to.equal(bidRequests[0]); expect(requests[1].bidRequest).to.equal(bidRequests[1]); }); @@ -137,7 +137,7 @@ describe('BeachfrontAdapter', function () { it('should create a POST request for each bid', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); expect(requests[0].method).to.equal('POST'); expect(requests[0].url).to.equal(VIDEO_ENDPOINT + bidRequest.params.appId); }); @@ -155,7 +155,7 @@ describe('BeachfrontAdapter', function () { const topLocation = parseUrl('http://www.example.com?foo=bar', { decodeSearchAsString: true }); const bidderRequest = { refererInfo: { - referer: topLocation.href + page: topLocation.href } }; const requests = spec.buildRequests([ bidRequest ], bidderRequest); @@ -176,7 +176,7 @@ describe('BeachfrontAdapter', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; bidRequest.getFloor = () => ({ currency: 'USD', floor: 1.16 }); - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.imp[0].bidfloor).to.equal(1.16); }); @@ -185,7 +185,7 @@ describe('BeachfrontAdapter', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; bidRequest.getFloor = () => ({}); - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); }); @@ -199,7 +199,7 @@ describe('BeachfrontAdapter', function () { playerSize: [[ width, height ]] } }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.imp[0].video).to.deep.contain({ w: width, h: height }); }); @@ -213,7 +213,7 @@ describe('BeachfrontAdapter', function () { playerSize: `${width}x${height}` } }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.imp[0].video).to.deep.contain({ w: width, h: height }); }); @@ -225,7 +225,7 @@ describe('BeachfrontAdapter', function () { playerSize: [] } }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.imp[0].video).to.deep.contain({ w: undefined, h: undefined }); }); @@ -236,7 +236,7 @@ describe('BeachfrontAdapter', function () { const bidRequest = bidRequests[0]; bidRequest.sizes = [ width, height ]; bidRequest.mediaTypes = { video: {} }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.imp[0].video).to.deep.contain({ w: width, h: height }); }); @@ -251,7 +251,7 @@ describe('BeachfrontAdapter', function () { bidRequest.mediaTypes = { video: { mimes, playbackmethod, maxduration, placement, skip } }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); }); @@ -265,7 +265,7 @@ describe('BeachfrontAdapter', function () { const skip = 1; bidRequest.mediaTypes = { video: { placement: 3, skip: 0 } }; bidRequest.params.video = { mimes, playbackmethod, maxduration, placement, skip }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); }); @@ -312,7 +312,7 @@ describe('BeachfrontAdapter', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; bidRequest.schain = schain; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.source.ext.schain).to.deep.equal(schain); }); @@ -322,12 +322,12 @@ describe('BeachfrontAdapter', function () { tdid: '54017816', idl_env: '13024996', uid2: { id: '45843401' }, - haloId: { haloId: '60314917', auSeg: ['segment1', 'segment2'] } + hadronId: { hadronId: '60314917', auSeg: ['segment1', 'segment2'] } }; const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; bidRequest.userId = userId; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.user.ext.eids).to.deep.equal([ { @@ -360,7 +360,7 @@ describe('BeachfrontAdapter', function () { { source: 'audigent.com', uids: [{ - id: userId.haloId, + id: userId.hadronId, atype: 1, }] } @@ -372,14 +372,14 @@ describe('BeachfrontAdapter', function () { it('should attach the bid requests array', function () { bidRequests[0].mediaTypes = { banner: {} }; bidRequests[1].mediaTypes = { banner: {} }; - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, {}); expect(requests[0].bidRequest).to.deep.equal(bidRequests); }); it('should create a single POST request for all bids', function () { bidRequests[0].mediaTypes = { banner: {} }; bidRequests[1].mediaTypes = { banner: {} }; - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, {}); expect(requests.length).to.equal(1); expect(requests[0].method).to.equal('POST'); expect(requests[0].url).to.equal(BANNER_ENDPOINT); @@ -398,7 +398,7 @@ describe('BeachfrontAdapter', function () { const topLocation = parseUrl('http://www.example.com?foo=bar', { decodeSearchAsString: true }); const bidderRequest = { refererInfo: { - referer: topLocation.href + page: topLocation.href } }; const requests = spec.buildRequests([ bidRequest ], bidderRequest); @@ -422,7 +422,7 @@ describe('BeachfrontAdapter', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { banner: {} }; bidRequest.getFloor = () => ({ currency: 'USD', floor: 1.16 }); - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.slots[0].bidfloor).to.equal(1.16); }); @@ -431,7 +431,7 @@ describe('BeachfrontAdapter', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { banner: {} }; bidRequest.getFloor = () => ({}); - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.slots[0].bidfloor).to.equal(bidRequest.params.bidfloor); }); @@ -445,7 +445,7 @@ describe('BeachfrontAdapter', function () { sizes: [[ width, height ]] } }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.slots[0].sizes).to.deep.equal([ { w: width, h: height } @@ -461,7 +461,7 @@ describe('BeachfrontAdapter', function () { sizes: `${width}x${height}` } }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.slots[0].sizes).to.deep.equal([ { w: width, h: height } @@ -475,7 +475,7 @@ describe('BeachfrontAdapter', function () { sizes: [] } }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.slots[0].sizes).to.deep.equal([]); }); @@ -486,7 +486,7 @@ describe('BeachfrontAdapter', function () { const bidRequest = bidRequests[0]; bidRequest.sizes = [ width, height ]; bidRequest.mediaTypes = { banner: {} }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.slots[0].sizes).to.deep.contain({ w: width, h: height }); }); @@ -533,7 +533,7 @@ describe('BeachfrontAdapter', function () { const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { banner: {} }; bidRequest.schain = schain; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.schain).to.deep.equal(schain); }); @@ -543,51 +543,38 @@ describe('BeachfrontAdapter', function () { tdid: '54017816', idl_env: '13024996', uid2: { id: '45843401' }, - haloId: { haloId: '60314917', auSeg: ['segment1', 'segment2'] } + hadronId: { hadronId: '60314917', auSeg: ['segment1', 'segment2'] } }; const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { banner: {} }; bidRequest.userId = userId; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; expect(data.tdid).to.equal(userId.tdid); expect(data.idl).to.equal(userId.idl_env); expect(data.uid2).to.equal(userId.uid2.id); - expect(data.haloid).to.equal(userId.haloId); + expect(data.hadronid).to.equal(userId.hadronId); }); }); describe('with first-party data', function () { - let sandbox - - beforeEach(function () { - sandbox = sinon.sandbox.create(); - }); - - afterEach(function () { - sandbox.restore(); - }); - it('must add first-party data to the video bid request', function () { - sandbox.stub(config, 'getConfig').callsFake(key => { - const cfg = { - ortb2: { - site: { - keywords: 'test keyword' - }, - user: { - data: 'some user data' - } - } - }; - return deepAccess(cfg, key); - }); + const ortb2 = { + site: { + keywords: 'test keyword' + }, + user: { + data: 'some user data' + } + }; + const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { video: {} }; const bidderRequest = { refererInfo: { - referer: 'http://example.com/page.html' - } + page: 'http://example.com/page.html' + }, + ortb2 }; const requests = spec.buildRequests([ bidRequest ], bidderRequest); const data = requests[0].data; @@ -598,22 +585,17 @@ describe('BeachfrontAdapter', function () { }); it('must add first-party data to the banner bid request', function () { - sandbox.stub(config, 'getConfig').callsFake(key => { - const cfg = { - ortb2: { - site: { - keywords: 'test keyword' - }, - user: { - data: 'some user data' - } - } - }; - return deepAccess(cfg, key); - }); + const ortb2 = { + site: { + keywords: 'test keyword' + }, + user: { + data: 'some user data' + } + }; const bidRequest = bidRequests[0]; bidRequest.mediaTypes = { banner: {} }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {ortb2}); const data = requests[0].data; expect(data.ortb2.user.data).to.equal('some user data'); expect(data.ortb2.site.keywords).to.equal('test keyword'); @@ -643,7 +625,7 @@ describe('BeachfrontAdapter', function () { appId: '3b16770b-17af-4d22-daff-9606bdf2c9c3' } }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); expect(requests.length).to.equal(2); expect(requests[0].url).to.contain(VIDEO_ENDPOINT); expect(requests[1].url).to.contain(BANNER_ENDPOINT); @@ -669,7 +651,7 @@ describe('BeachfrontAdapter', function () { appId: '3b16770b-17af-4d22-daff-9606bdf2c9c3' } }; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], {}); expect(requests[0].data.imp[0].video).to.deep.contain({ w: 640, h: 360 }); expect(requests[1].data.slots[0].sizes).to.deep.equal([{ w: 300, h: 250 }]); }); diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index 9de13afc2ee..c6001c3ba2e 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -204,5 +204,16 @@ describe('BeOp Bid Adapter tests', () => { expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=won'); expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('pid=5a8af500c9e77c00017e4cad'); }); + it('should call triggerPixel utils function on bid won and work even if params is an array', function () { + spec.onBidWon({}); + spec.onBidWon(); + expect(triggerPixelStub.getCall(0)).to.be.null; + spec.onBidWon({params: [{accountId: '5a8af500c9e77c00017e4cad'}], cpm: 1.2}); + expect(triggerPixelStub.getCall(0)).to.not.be.null; + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://t.beop.io'); + expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ca=bid'); + expect(triggerPixelStub.getCall(0).args[0]).to.include('se_ac=won'); + expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('pid=5a8af500c9e77c00017e4cad'); + }); }); }); diff --git a/test/spec/modules/betweenBidAdapter_spec.js b/test/spec/modules/betweenBidAdapter_spec.js index 3baa92e35d5..a4b89ab1b65 100644 --- a/test/spec/modules/betweenBidAdapter_spec.js +++ b/test/spec/modules/betweenBidAdapter_spec.js @@ -85,6 +85,23 @@ describe('betweenBidAdapterTests', function () { expect(req_data.cur).to.equal('THX'); }); + it('validate default cur USD', function() { + let bidRequestData = [{ + bidId: 'bid1234', + bidder: 'between', + params: { + w: 240, + h: 400, + s: 1112 + }, + sizes: [[240, 400]] + }]; + + let request = spec.buildRequests(bidRequestData); + let req_data = JSON.parse(request.data)[0].data; + + expect(req_data.cur).to.equal('USD'); + }); it('validate subid param', function() { let bidRequestData = [{ bidId: 'bid1234', diff --git a/test/spec/modules/bidwatchAnalyticsAdapter_spec.js b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js index 1a322d131a9..321d3607178 100644 --- a/test/spec/modules/bidwatchAnalyticsAdapter_spec.js +++ b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js @@ -106,7 +106,8 @@ describe('BidWatch Analytics', function () { 'gdprConsent': { 'consentString': 'CONSENT', 'gdprApplies': true, - 'apiVersion': 2 + 'apiVersion': 2, + 'vendorData': 'a lot of borring stuff', }, 'start': 1647424261189 }, @@ -259,15 +260,15 @@ describe('BidWatch Analytics', function () { describe('main test flow', function () { beforeEach(function () { sinon.stub(events, 'getEvents').returns([]); + sinon.spy(bidwatchAnalytics, 'track'); }); - afterEach(function () { events.getEvents.restore(); + bidwatchAnalytics.disableAnalytics(); + bidwatchAnalytics.track.restore(); }); - it('should catch events of interest', function () { - sinon.spy(bidwatchAnalytics, 'track'); - + it('test auctionEnd', function () { adapterManager.registerAnalyticsAdapter({ code: 'bidwatch', adapter: bidwatchAnalytics @@ -279,10 +280,43 @@ describe('BidWatch Analytics', function () { domain: 'test' } }); + + events.emit(constants.EVENTS.BID_REQUESTED, auctionEnd['bidderRequests'][0]); + events.emit(constants.EVENTS.BID_RESPONSE, auctionEnd['bidsReceived'][0]); events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.have.property('auctionEnd').exist; + expect(message.auctionEnd).to.have.lengthOf(1); + expect(message.auctionEnd[0]).to.have.property('bidsReceived').and.to.have.lengthOf(1); + expect(message.auctionEnd[0].bidsReceived[0]).not.to.have.property('ad'); + expect(message.auctionEnd[0].bidsReceived[0]).to.have.property('meta'); + expect(message.auctionEnd[0].bidsReceived[0].meta).to.have.property('advertiserDomains'); + expect(message.auctionEnd[0]).to.have.property('bidderRequests').and.to.have.lengthOf(1); + expect(message.auctionEnd[0].bidderRequests[0]).to.have.property('gdprConsent'); + expect(message.auctionEnd[0].bidderRequests[0].gdprConsent).not.to.have.property('vendorData'); + sinon.assert.callCount(bidwatchAnalytics.track, 4); + }); + + it('test bidWon', function() { + adapterManager.registerAnalyticsAdapter({ + code: 'bidwatch', + adapter: bidwatchAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'bidwatch', + options: { + domain: 'test' + } + }); events.emit(constants.EVENTS.BID_WON, bidWon); - sinon.assert.callCount(bidwatchAnalytics.track, 3); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).not.to.have.property('ad'); + expect(message).to.have.property('cpmIncrement').and.to.equal(27.4276); + sinon.assert.callCount(bidwatchAnalytics.track, 1); }); }); }); diff --git a/test/spec/modules/big-richmediaBidAdapter_spec.js b/test/spec/modules/big-richmediaBidAdapter_spec.js index 1e97e1ac1d7..b2ee0725d49 100644 --- a/test/spec/modules/big-richmediaBidAdapter_spec.js +++ b/test/spec/modules/big-richmediaBidAdapter_spec.js @@ -190,6 +190,7 @@ describe('bigRichMediaAdapterTests', function () { it('should get correct bid response', function () { const expectedResponse = [ { + 'adId': '3a1f23123e', 'requestId': '3db3773286ee59', 'cpm': 0.5, 'creativeId': 29681110, diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index 729605f7db8..b8994b86847 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { spec, buildBid, BLIINK_ENDPOINT_ENGINE, parseXML, getMetaList } from 'modules/bliinkBidAdapter.js' +import { spec, buildBid, BLIINK_ENDPOINT_ENGINE, getMetaList, BLIINK_ENDPOINT_COOKIE_SYNC_IFRAME } from 'modules/bliinkBidAdapter.js' /** * @description Mockup bidRequest @@ -33,6 +33,7 @@ const getConfigBid = (placement) => { crumbs: { pubcid: '55ffadc5-051f-428d-8ecc-dc585e0bde0d' }, + sizes: [[300, 250]], mediaTypes: { banner: { sizes: [ @@ -51,13 +52,55 @@ const getConfigBid = (placement) => { placement: placement, tagId: '14f30eca-85d2-11e8-9eed-0242ac120007' }, - sizes: [ - [300, 250] - ], src: 'client', transactionId: 'cc6678c4-9746-4082-b9e2-d8065d078ebf' } } +const getConfigBannerBid = () => { + return { + creative: { + banner: { + adm: '', + height: 250, + width: 300, + }, + media_type: 'banner', + creativeId: 125, + }, + price: 1, + id: '810', + token: 'token', + mode: 'rtb', + extras: { + deal_id: '34567erty', + transaction_id: '2def0c5b2a7f6e', + }, + currency: 'EUR', + } +} +const getConfigVideoBid = () => { + return { + creative: { + video: { + content: + '', + height: 250, + width: 300, + }, + media_type: 'video', + creativeId: 0, + }, + price: 1, + id: '8121', + token: 'token', + mode: 'rtb', + extras: { + deal_id: '34567ertyRTY', + transaction_id: '2def0c5b2a7f6e', + }, + currency: 'EUR', + } +} /** * @description Mockup response from engine.bliink.io/xxxx @@ -80,33 +123,30 @@ const getConfigBid = (placement) => { */ const getConfigCreative = () => { return { - ad_id: 5648, - price: 1, + ad: '', + mediaType: 'banner', + cpm: 4, currency: 'EUR', - media_type: 'banner', - category: 1, - id: 2825, - creativeId: 2825, - type: 1, - viewability_duration: 1, - viewability_percent_in_view: 30, - content: { - creative: { - adm: '', - } - } + creativeId: '34567erty', + width: 300, + height: 250, + ttl: 3600, + netRevenue: true, } } -const getConfigCreativeVideo = () => { +const getConfigCreativeVideo = (isNoVast) => { return { - ad_id: 5648, - price: 1, + mediaType: 'video', + vastXml: isNoVast ? '' : '', + cpm: 0, currency: 'EUR', - media_type: 'video', - category: 1, - creativeId: 2825, - content: '' + creativeId: '34567ertyaza', + requestId: '6a204ce130280d', + width: 300, + height: 250, + ttl: 3600, + netRevenue: true, } } @@ -124,7 +164,7 @@ const getConfigBuildRequest = (placement) => { isAmp: false, numIframes: 0, reachedTop: true, - referer: 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + page: 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', }, } @@ -155,8 +195,9 @@ const getConfigInterpretResponse = (noAd = false) => { return { body: { - creative: getConfigCreative(), + ...getConfigCreative(), mode: 'ad', + transactionId: '2def0c5b2a7f6e', token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjgxNzA4MzEsImlhdCI6MTYyNzU2NjAzMSwiaXNzIjoiYmxpaW5rIiwiZGF0YSI6eyJ0eXBlIjoiYWQtc2VydmVyIiwidHJhbnNhY3Rpb25JZCI6IjM1YmU1NDNjLTNkZTQtNGQ1Yy04N2NjLWIzYzEyOGZiYzU0MCIsIm5ldHdvcmtJZCI6MjEsInNpdGVJZCI6NTksInRhZ0lkIjo1OSwiY29va2llSWQiOiJjNGU4MWVhOS1jMjhmLTQwZDItODY1ZC1hNjQzZjE1OTcyZjUiLCJldmVudElkIjozLCJ0YXJnZXRpbmciOnsicGxhdGZvcm0iOiJXZWJzaXRlIiwiaXAiOiI3OC4xMjIuNzUuNzIiLCJ0aW1lIjoxNjI3NTY2MDMxLCJsb2NhdGlvbiI6eyJsYXRpdHVkZSI6NDguOTczOSwibG9uZ2l0dWRlIjozLjMxMTMsInJlZ2lvbiI6IkhERiIsImNvdW50cnkiOiJGUiIsImNpdHkiOiJTYXVsY2hlcnkiLCJ6aXBDb2RlIjoiMDIzMTAiLCJkZXBhcnRtZW50IjoiMDIifSwiY2l0eSI6IlNhdWxjaGVyeSIsImNvdW50cnkiOiJGUiIsImRldmljZU9zIjoibWFjT1MiLCJkZXZpY2VQbGF0Zm9ybSI6IldlYnNpdGUiLCJyYXdVc2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvOTEuMC40NDcyLjEyNCBTYWZhcmkvNTM3LjM2In0sImdkcHIiOnsiaGFzQ29uc2VudCI6dHJ1ZX0sIndpbiI6ZmFsc2UsImFkSWQiOjU2NDgsImFkdmVydGlzZXJJZCI6MSwiY2FtcGFpZ25JZCI6MSwiY3JlYXRpdmVJZCI6MjgyNSwiZXJyb3IiOmZhbHNlfX0.-UefQH4G0k-RJGemBYffs-KL7EEwma2Wuwgk2xnpij8' }, headers: {}, @@ -168,7 +209,7 @@ const getConfigInterpretResponse = (noAd = false) => { * @param noAd * @return {{body: string} | {mode: string, message: string}} */ -const getConfigInterpretResponseRTB = (noAd = false) => { +const getConfigInterpretResponseRTB = (noAd = false, isInvalidVast = false) => { if (noAd) { return { message: 'invalid tag', @@ -176,20 +217,51 @@ const getConfigInterpretResponseRTB = (noAd = false) => { } } + const validVast = ` + + + + BLIINK + https://vast.bliink.io/p/508379d0-9f65-4198-8ba5-f61f2b51224f.xml + https://e.api.bliink.io/e?name=vast-error&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzQwMzA1MjcsImlhdCI6MTYzMzQyNTcyNywiaXNzIjoiYmxpaW5rIiwiZGF0YSI6eyJ0eXBlIjoiYWQtc2VydmVyIiwidHJhbnNhY3Rpb25JZCI6ImE2NjJjZGJmLTkzNDYtNDI0MS1iMTU0LTJhOTc2OTg0NjNmOSIsIm5ldHdvcmtJZCI6MjUsInNpdGVJZCI6MTQzLCJ0YWdJZCI6MTI3MSwiY29va2llSWQiOiIwNWFhN2UwMi05MzgzLTQ1NGYtOTJmZC1jOTE2YWNlMmUyZjYiLCJldmVudElkIjozLCJ0YXJnZXRpbmciOnsicGxhdGZvcm0iOiJXZWJzaXRlIiwicmVmZXJyZXIiOiJodHRwOi8vbG9jYWxob3N0OjgxODEvaW50ZWdyYXRpb25FeGFtcGxlcy9ncHQvYmxpaW5rLWluc3RyZWFtLmh0bWwiLCJwYWdlVXJsIjoiaHR0cDovL2xvY2FsaG9zdDo4MTgxL2ludGVncmF0aW9uRXhhbXBsZXMvZ3B0L2JsaWluay1pbnN0cmVhbS5odG1sIiwiaXAiOiIzMS4zOS4xNDEuMTQwIiwidGltZSI6MTYzMzQyNTcyNywibG9jYXRpb24iOnsibGF0aXR1ZGUiOjQ4Ljk0MjIsImxvbmdpdHVkZSI6Mi41MDM5LCJyZWdpb24iOiJJREYiLCJjb3VudHJ5IjoiRlIiLCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsInppcENvZGUiOiI5MzYwMCIsImRlcGFydG1lbnQiOiI5MyJ9LCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsImNvdW50cnkiOiJGUiIsImRldmljZU9zIjoibWFjT1MiLCJkZXZpY2VQbGF0Zm9ybSI6IldlYnNpdGUiLCJyYXdVc2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvOTMuMC40NTc3LjYzIFNhZmFyaS81MzcuMzYiLCJjb250ZW50Q2xhc3NpZmljYXRpb24iOnsiYnJhbmRzYWZlIjpmYWxzZX19LCJnZHByIjp7Imhhc0NvbnNlbnQiOnRydWV9LCJ3aW4iOmZhbHNlLCJhZElkIjo1NzkzLCJhZHZlcnRpc2VySWQiOjEsImNhbXBhaWduSWQiOjEsImNyZWF0aXZlSWQiOjExOTQsImVycm9yIjpmYWxzZX19.nJSJPKovg0_jSHtLdrMPDqesAIlFKCuXPXYxpsyWBDw + https://e.api.bliink.io/e?name=impression&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzQwMzA1MjcsImlhdCI6MTYzMzQyNTcyNywiaXNzIjoiYmxpaW5rIiwiZGF0YSI6eyJ0eXBlIjoiYWQtc2VydmVyIiwidHJhbnNhY3Rpb25JZCI6ImE2NjJjZGJmLTkzNDYtNDI0MS1iMTU0LTJhOTc2OTg0NjNmOSIsIm5ldHdvcmtJZCI6MjUsInNpdGVJZCI6MTQzLCJ0YWdJZCI6MTI3MSwiY29va2llSWQiOiIwNWFhN2UwMi05MzgzLTQ1NGYtOTJmZC1jOTE2YWNlMmUyZjYiLCJldmVudElkIjozLCJ0YXJnZXRpbmciOnsicGxhdGZvcm0iOiJXZWJzaXRlIiwicmVmZXJyZXIiOiJodHRwOi8vbG9jYWxob3N0OjgxODEvaW50ZWdyYXRpb25FeGFtcGxlcy9ncHQvYmxpaW5rLWluc3RyZWFtLmh0bWwiLCJwYWdlVXJsIjoiaHR0cDovL2xvY2FsaG9zdDo4MTgxL2ludGVncmF0aW9uRXhhbXBsZXMvZ3B0L2JsaWluay1pbnN0cmVhbS5odG1sIiwiaXAiOiIzMS4zOS4xNDEuMTQwIiwidGltZSI6MTYzMzQyNTcyNywibG9jYXRpb24iOnsibGF0aXR1ZGUiOjQ4Ljk0MjIsImxvbmdpdHVkZSI6Mi41MDM5LCJyZWdpb24iOiJJREYiLCJjb3VudHJ5IjoiRlIiLCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsInppcENvZGUiOiI5MzYwMCIsImRlcGFydG1lbnQiOiI5MyJ9LCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsImNvdW50cnkiOiJGUiIsImRldmljZU9zIjoibWFjT1MiLCJkZXZpY2VQbGF0Zm9ybSI6IldlYnNpdGUiLCJyYXdVc2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvOTMuMC40NTc3LjYzIFNhZmFyaS81MzcuMzYiLCJjb250ZW50Q2xhc3NpZmljYXRpb24iOnsiYnJhbmRzYWZlIjpmYWxzZX19LCJnZHByIjp7Imhhc0NvbnNlbnQiOnRydWV9LCJ3aW4iOmZhbHNlLCJhZElkIjo1NzkzLCJhZHZlcnRpc2VySWQiOjEsImNhbXBhaWduSWQiOjEsImNyZWF0aXZlSWQiOjExOTQsImVycm9yIjpmYWxzZX19.nJSJPKovg0_jSHtLdrMPDqesAIlFKCuXPXYxpsyWBDw + 1EUR + + + + ` + const invalidVast = ` + + + + + + ` + return { - body: ` - - - - BLIINK - https://vast.bliink.io/p/508379d0-9f65-4198-8ba5-f61f2b51224f.xml - https://e.api.bliink.io/e?name=vast-error&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzQwMzA1MjcsImlhdCI6MTYzMzQyNTcyNywiaXNzIjoiYmxpaW5rIiwiZGF0YSI6eyJ0eXBlIjoiYWQtc2VydmVyIiwidHJhbnNhY3Rpb25JZCI6ImE2NjJjZGJmLTkzNDYtNDI0MS1iMTU0LTJhOTc2OTg0NjNmOSIsIm5ldHdvcmtJZCI6MjUsInNpdGVJZCI6MTQzLCJ0YWdJZCI6MTI3MSwiY29va2llSWQiOiIwNWFhN2UwMi05MzgzLTQ1NGYtOTJmZC1jOTE2YWNlMmUyZjYiLCJldmVudElkIjozLCJ0YXJnZXRpbmciOnsicGxhdGZvcm0iOiJXZWJzaXRlIiwicmVmZXJyZXIiOiJodHRwOi8vbG9jYWxob3N0OjgxODEvaW50ZWdyYXRpb25FeGFtcGxlcy9ncHQvYmxpaW5rLWluc3RyZWFtLmh0bWwiLCJwYWdlVXJsIjoiaHR0cDovL2xvY2FsaG9zdDo4MTgxL2ludGVncmF0aW9uRXhhbXBsZXMvZ3B0L2JsaWluay1pbnN0cmVhbS5odG1sIiwiaXAiOiIzMS4zOS4xNDEuMTQwIiwidGltZSI6MTYzMzQyNTcyNywibG9jYXRpb24iOnsibGF0aXR1ZGUiOjQ4Ljk0MjIsImxvbmdpdHVkZSI6Mi41MDM5LCJyZWdpb24iOiJJREYiLCJjb3VudHJ5IjoiRlIiLCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsInppcENvZGUiOiI5MzYwMCIsImRlcGFydG1lbnQiOiI5MyJ9LCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsImNvdW50cnkiOiJGUiIsImRldmljZU9zIjoibWFjT1MiLCJkZXZpY2VQbGF0Zm9ybSI6IldlYnNpdGUiLCJyYXdVc2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvOTMuMC40NTc3LjYzIFNhZmFyaS81MzcuMzYiLCJjb250ZW50Q2xhc3NpZmljYXRpb24iOnsiYnJhbmRzYWZlIjpmYWxzZX19LCJnZHByIjp7Imhhc0NvbnNlbnQiOnRydWV9LCJ3aW4iOmZhbHNlLCJhZElkIjo1NzkzLCJhZHZlcnRpc2VySWQiOjEsImNhbXBhaWduSWQiOjEsImNyZWF0aXZlSWQiOjExOTQsImVycm9yIjpmYWxzZX19.nJSJPKovg0_jSHtLdrMPDqesAIlFKCuXPXYxpsyWBDw - https://e.api.bliink.io/e?name=impression&token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzQwMzA1MjcsImlhdCI6MTYzMzQyNTcyNywiaXNzIjoiYmxpaW5rIiwiZGF0YSI6eyJ0eXBlIjoiYWQtc2VydmVyIiwidHJhbnNhY3Rpb25JZCI6ImE2NjJjZGJmLTkzNDYtNDI0MS1iMTU0LTJhOTc2OTg0NjNmOSIsIm5ldHdvcmtJZCI6MjUsInNpdGVJZCI6MTQzLCJ0YWdJZCI6MTI3MSwiY29va2llSWQiOiIwNWFhN2UwMi05MzgzLTQ1NGYtOTJmZC1jOTE2YWNlMmUyZjYiLCJldmVudElkIjozLCJ0YXJnZXRpbmciOnsicGxhdGZvcm0iOiJXZWJzaXRlIiwicmVmZXJyZXIiOiJodHRwOi8vbG9jYWxob3N0OjgxODEvaW50ZWdyYXRpb25FeGFtcGxlcy9ncHQvYmxpaW5rLWluc3RyZWFtLmh0bWwiLCJwYWdlVXJsIjoiaHR0cDovL2xvY2FsaG9zdDo4MTgxL2ludGVncmF0aW9uRXhhbXBsZXMvZ3B0L2JsaWluay1pbnN0cmVhbS5odG1sIiwiaXAiOiIzMS4zOS4xNDEuMTQwIiwidGltZSI6MTYzMzQyNTcyNywibG9jYXRpb24iOnsibGF0aXR1ZGUiOjQ4Ljk0MjIsImxvbmdpdHVkZSI6Mi41MDM5LCJyZWdpb24iOiJJREYiLCJjb3VudHJ5IjoiRlIiLCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsInppcENvZGUiOiI5MzYwMCIsImRlcGFydG1lbnQiOiI5MyJ9LCJjaXR5IjoiQXVsbmF5LXNvdXMtQm9pcyIsImNvdW50cnkiOiJGUiIsImRldmljZU9zIjoibWFjT1MiLCJkZXZpY2VQbGF0Zm9ybSI6IldlYnNpdGUiLCJyYXdVc2VyQWdlbnQiOiJNb3ppbGxhLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvOTMuMC40NTc3LjYzIFNhZmFyaS81MzcuMzYiLCJjb250ZW50Q2xhc3NpZmljYXRpb24iOnsiYnJhbmRzYWZlIjpmYWxzZX19LCJnZHByIjp7Imhhc0NvbnNlbnQiOnRydWV9LCJ3aW4iOmZhbHNlLCJhZElkIjo1NzkzLCJhZHZlcnRpc2VySWQiOjEsImNhbXBhaWduSWQiOjEsImNyZWF0aXZlSWQiOjExOTQsImVycm9yIjpmYWxzZX19.nJSJPKovg0_jSHtLdrMPDqesAIlFKCuXPXYxpsyWBDw - 1EUR - - - - ` + body: { bids: [ + { + 'creative': { + 'video': { + 'content': isInvalidVast ? invalidVast : validVast, + 'height': 250, + 'width': 300 + }, + 'media_type': 'video', + 'creativeId': 0, + }, + 'price': 0, + 'id': '8121', + 'token': 'token', + 'mode': 'rtb', + 'extras': { + 'deal_id': '34567ertyaza', + 'transaction_id': '2def0c5b2a7f6e' + }, + 'currency': 'EUR' + } + ], + userSyncs: []} } } @@ -258,31 +330,6 @@ describe('BLIINK Adapter getMetaList', function() { * @description Array of tests used in describe function below * @type {[{args: {fn: (string|Document)}, want: string, title: string}, {args: {fn: (string|Document)}, want: string, title: string}]} */ -const testsParseXML = [ - { - title: 'Should return null, if content length equal to 0', - args: { - fn: parseXML('') - }, - want: null, - }, - { - title: 'Should return null, if content isnt string', - args: { - fn: parseXML({}) - }, - want: null, - }, -] - -describe('BLIINK Adapter parseXML', function() { - for (const test of testsParseXML) { - it(test.title, () => { - const res = test.args.fn - expect(res).to.eql(test.want) - }) - } -}) /** * @@ -331,29 +378,67 @@ describe('BLIINK Adapter isBidRequestValid', function() { } }) +const vastXml = getConfigInterpretResponseRTB().body.bids[0].creative.video.content + const testsInterpretResponse = [ { title: 'Should construct bid for video instream', args: { - fn: spec.interpretResponse(getConfigInterpretResponseRTB(false), getConfigBuildRequest('video')) + fn: spec.interpretResponse(getConfigInterpretResponseRTB(false)) }, - want: { + want: [{ cpm: 0, currency: 'EUR', height: 250, width: 300, - creativeId: 0, + creativeId: '34567ertyaza', mediaType: 'video', - netRevenue: false, + netRevenue: true, requestId: '2def0c5b2a7f6e', ttl: 3600, - vastXml: getConfigInterpretResponseRTB().body, - } + vastXml, + vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(vastXml.replace(/\\"/g, '"')) + }] }, { title: 'ServerResponse with message: invalid tag, return empty array', args: { - fn: spec.interpretResponse(getConfigInterpretResponse(true), getConfigBuildRequest('banner')) + fn: spec.interpretResponse(getConfigInterpretResponse(true)) + }, + want: [] + }, + { + title: 'ServerResponse with mediaType banner', + args: { + fn: spec.interpretResponse({body: {bids: [getConfigBannerBid()]}}), + }, + want: [{ + ad: '', + cpm: 1, + creativeId: '34567erty', + currency: 'EUR', + height: 250, + mediaType: 'banner', + netRevenue: true, + requestId: '2def0c5b2a7f6e', + ttl: 3600, + width: 300 + }] + }, + { + title: 'ServerResponse with unhandled mediaType, return empty array', + args: { + fn: spec.interpretResponse({body: {bids: [{...getConfigBannerBid(), + creative: { + unknown: { + adm: '', + height: 250, + width: 300, + }, + media_type: 'unknown', + creativeId: 125, + requestId: '2def0c5b2a7f6e', + }}]}}), }, want: [] }, @@ -388,6 +473,7 @@ describe('BLIINK Adapter interpretResponse', function() { * } * }, want, title: string}]} */ + const testsBuildBid = [ { title: 'Should return null if no bid passed in parameters', @@ -406,7 +492,7 @@ const testsBuildBid = [ { title: 'input data respect the output model for video', args: { - fn: buildBid(getConfigBid('video'), getConfigCreativeVideo()) + fn: buildBid(getConfigVideoBid('video'), getConfigCreativeVideo()) }, want: { requestId: getConfigBid('video').bidId, @@ -415,16 +501,47 @@ const testsBuildBid = [ mediaType: 'video', width: 300, height: 250, - creativeId: getConfigCreativeVideo().creativeId, - netRevenue: false, - vastXml: getConfigCreativeVideo().content, + creativeId: getConfigVideoBid().extras.deal_id, + netRevenue: true, + vastXml: getConfigCreativeVideo().vastXml, + vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), + ttl: 3600, + } + }, + { + title: 'use default height width output model for video', + args: { + fn: buildBid({...getConfigVideoBid('video'), + creative: { + video: { + content: + '', + height: null, + width: null, + }, + media_type: 'video', + creativeId: getConfigVideoBid().extras.deal_id, + requestId: '2def0c5b2a7f6e', + }}, getConfigCreativeVideo()) + }, + want: { + requestId: getConfigBid('video').bidId, + cpm: 1, + currency: 'EUR', + mediaType: 'video', + width: 1, + height: 1, + creativeId: getConfigVideoBid().extras.deal_id, + netRevenue: true, + vastXml: getConfigCreativeVideo().vastXml, + vastUrl: 'data:text/xml;charset=utf-8;base64,' + btoa(getConfigCreativeVideo().vastXml.replace(/\\"/g, '"')), ttl: 3600, } }, { title: 'input data respect the output model for banner', args: { - fn: buildBid(getConfigBid('banner'), getConfigCreative()) + fn: buildBid(getConfigBannerBid()) }, want: { requestId: getConfigBid('banner').bidId, @@ -433,10 +550,10 @@ const testsBuildBid = [ mediaType: 'banner', width: 300, height: 250, - creativeId: getConfigCreative().id, - ad: getConfigCreative().content.creative.adm, + creativeId: getConfigCreative().creativeId, + ad: getConfigBannerBid().creative.banner.adm, ttl: 3600, - netRevenue: false, + netRevenue: true, } } ] @@ -468,23 +585,27 @@ const testsBuildRequests = [ fn: spec.buildRequests([], getConfigBuildRequest('banner')) }, want: { - method: 'GET', - url: `${BLIINK_ENDPOINT_ENGINE}/${getConfigBuildRequest('banner').bids[0].params.tagId}`, - params: { - bidderRequestId: getConfigBuildRequest('banner').bidderRequestId, - bidderCode: getConfigBuildRequest('banner').bidderCode, - bids: getConfigBuildRequest('banner').bids, - refererInfo: getConfigBuildRequest('banner').refererInfo - }, + method: 'POST', + url: BLIINK_ENDPOINT_ENGINE, data: { - gdpr: false, - gdpr_consent: '', - height: 250, - width: 300, keywords: '', pageDescription: '', pageTitle: '', pageUrl: 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + tags: [ + { + transactionId: '2def0c5b2a7f6e', + id: '14f30eca-85d2-11e8-9eed-0242ac120007', + imageUrl: '', + mediaTypes: ['banner'], + sizes: [ + { + h: 250, + w: 300, + }, + ], + }, + ] } } }, @@ -499,23 +620,83 @@ const testsBuildRequests = [ })) }, want: { - method: 'GET', - url: `${BLIINK_ENDPOINT_ENGINE}/${getConfigBuildRequest('banner').bids[0].params.tagId}`, - params: { - bidderRequestId: getConfigBuildRequest('banner').bidderRequestId, - bidderCode: getConfigBuildRequest('banner').bidderCode, - bids: getConfigBuildRequest('banner').bids, - refererInfo: getConfigBuildRequest('banner').refererInfo - }, + method: 'POST', + url: BLIINK_ENDPOINT_ENGINE, data: { gdpr: true, - gdpr_consent: 'XXXX', + gdprConsent: 'XXXX', pageDescription: '', pageTitle: '', keywords: '', pageUrl: 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', - height: 250, - width: 300, + tags: [ + { + transactionId: '2def0c5b2a7f6e', + id: '14f30eca-85d2-11e8-9eed-0242ac120007', + imageUrl: '', + mediaTypes: ['banner'], + sizes: [ + { + h: 250, + w: 300, + }, + ], + }, + ] + } + } + }, + { + title: 'Should build request width schain if exists', + args: { + fn: spec.buildRequests([{schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'ssp.test', + sid: '00001', + hp: 1 + }] + }}], Object.assign(getConfigBuildRequest('banner'), { + gdprConsent: { + gdprApplies: true, + consentString: 'XXXX' + }, + })) + }, + want: { + method: 'POST', + url: BLIINK_ENDPOINT_ENGINE, + data: { + gdpr: true, + gdprConsent: 'XXXX', + pageDescription: '', + pageTitle: '', + keywords: '', + pageUrl: 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'ssp.test', + sid: '00001', + hp: 1 + }] + }, + tags: [ + { + transactionId: '2def0c5b2a7f6e', + id: '14f30eca-85d2-11e8-9eed-0242ac120007', + imageUrl: '', + mediaTypes: ['banner'], + sizes: [ + { + h: 250, + w: 300, + }, + ], + }, + ] } } } @@ -530,7 +711,7 @@ describe('BLIINK Adapter buildRequests', function() { } }) -const getSyncOptions = (pixelEnabled = true, iframeEnabled = 'false') => { +const getSyncOptions = (pixelEnabled = true, iframeEnabled = false) => { return { pixelEnabled, iframeEnabled @@ -540,7 +721,15 @@ const getSyncOptions = (pixelEnabled = true, iframeEnabled = 'false') => { const getServerResponses = () => { return [ { - body: '', + body: {bids: [], + userSyncs: [ { + type: 'script', + url: 'https://prg.smartadserver.com/ac?out=js&nwid=3392&siteid=305791&pgname=rg&fmtid=81127&tgt=[sas_target]&visit=m&tmstp=[timestamp]&clcturl=[countgo]' + }, + { + type: 'image', + url: 'https://sync.smartadserver.com/getuid?nwid=3392&consentString=XXX&url=https%3A%2F%2Fcookiesync.api.bliink.io%2Fcookiesync%3Fpartner%3Dsmart%26uid%3D%5Bsas_uid%5D' + }]}, } ] } @@ -548,7 +737,8 @@ const getServerResponses = () => { const getGdprConsent = () => { return { gdprApplies: 1, - consentString: 'XXX' + consentString: 'XXX', + apiVersion: 2 } } @@ -566,39 +756,39 @@ const testsGetUserSyncs = [ { type: 'image', url: 'https://sync.smartadserver.com/getuid?nwid=3392&consentString=XXX&url=https%3A%2F%2Fcookiesync.api.bliink.io%2Fcookiesync%3Fpartner%3Dsmart%26uid%3D%5Bsas_uid%5D' - }, - { - type: 'image', - url: 'https://ad.360yield.com/server_match?partner_id=1531&consentString=XXX&r=https%3A%2F%2Fcookiesync.api.bliink.io%2Fcookiesync%3Fpartner%3Dazerion%26uid%3D%7BPUB_USER_ID%7D', - }, - { - type: 'image', - url: 'https://ads.stickyadstv.com/auto-user-sync?consentString=XXX', - }, - { - type: 'image', - url: 'https://cookiesync.api.bliink.io/getuid?url=https%3A%2F%2Fvisitor.omnitagjs.com%2Fvisitor%2Fsync%3Fuid%3D1625272249969090bb9d544bd6d8d645%26name%3DBLIINK%26visitor%3D%24UID%26external%3Dtrue&consentString=XXX', - }, - { - type: 'image', - url: 'https://cookiesync.api.bliink.io/getuid?url=https://pixel.advertising.com/ups/58444/sync?&gdpr=1&gdpr_consent=XXX&redir=true&uid=$UID', - }, + } + ] + }, + { + title: 'Should return iframe cookie sync if iframeEnabled', + args: { + fn: spec.getUserSyncs(getSyncOptions(true, true), getServerResponses(), getGdprConsent()) + }, + want: [ { - type: 'image', - url: 'https://ups.analytics.yahoo.com/ups/58499/occ?gdpr=1&gdpr_consent=XXX', + type: 'iframe', + url: `${BLIINK_ENDPOINT_COOKIE_SYNC_IFRAME}?gdpr=${getGdprConsent().gdprApplies}&coppa=0&gdprConsent=${getGdprConsent().consentString}&apiVersion=${getGdprConsent().apiVersion}` }, + ] + }, + { + title: 'ccpa', + args: { + fn: spec.getUserSyncs(getSyncOptions(true, true), getServerResponses(), getGdprConsent(), 'ccpa-consent') + }, + want: [ { - type: 'image', - url: 'https://secure.adnxs.com/getuid?https%3A%2F%2Fcookiesync.api.bliink.io%2Fcookiesync%3Fpartner%3Dazerion%26uid%3D%24UID', + type: 'iframe', + url: `${BLIINK_ENDPOINT_COOKIE_SYNC_IFRAME}?gdpr=${getGdprConsent().gdprApplies}&coppa=0&uspConsent=ccpa-consent&gdprConsent=${getGdprConsent().consentString}&apiVersion=${getGdprConsent().apiVersion}` }, ] }, { - title: 'Should not have gdpr consent', + title: 'Should output sync if no gdprConsent', args: { fn: spec.getUserSyncs(getSyncOptions(), getServerResponses()) }, - want: [] + want: getServerResponses()[0].body.userSyncs } ] diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js index c831ddf6ddf..0a9c6b30c94 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -439,7 +439,7 @@ describe('BlueBillywigAdapter', () => { it('should add referrerInfo as site when no app is set', () => { const newValidBidderRequest = deepClone(validBidderRequest); - newValidBidderRequest.refererInfo = { referer: 'https://www.bluebillywig.com' }; + newValidBidderRequest.refererInfo = { page: 'https://www.bluebillywig.com' }; const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); const payload = JSON.parse(request.data); diff --git a/test/spec/modules/blueconicRtdProvider_spec.js b/test/spec/modules/blueconicRtdProvider_spec.js new file mode 100644 index 00000000000..174c1e58997 --- /dev/null +++ b/test/spec/modules/blueconicRtdProvider_spec.js @@ -0,0 +1,135 @@ +import {config} from 'src/config.js'; +import {RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, blueconicSubmodule, storage} from 'modules/blueconicRtdProvider.js'; + +describe('blueconicRtdProvider', function() { + let getDataFromLocalStorageStub; + beforeEach(function() { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + describe('blueconicSubmodule', function() { + it('successfully instantiates', function () { + expect(blueconicSubmodule.init()).to.equal(true); + }); + }); + + describe('Add Blueconic Real-Time Data', function() { + it('merges ortb2Fragment data', function() { + const setConfigUserObj1 = { + name: 'www.dataprovider1.com', + ext: {segtax: 1}, + segment: [{id: '1776'}] + }; + const setConfigUserObj2 = { + name: 'www.dataprovider2.com', + ext: {segtax: 1}, + segment: [{id: '1914'} + ] + }; + + let bidConfig = { + ortb2Fragments: { + global: { + user: { + data: [setConfigUserObj1, setConfigUserObj2] + } + } + } + }; + + const rtdUserObj1 = { + name: 'www.dataprovider4.com', + ext: {segtax: 1}, + segment: [{id: '1918'}, {id: '1939'} + ] + }; + + const rtd = { + ortb2: { + user: { + data: [rtdUserObj1] + } + } + }; + + addRealTimeData(bidConfig.ortb2Fragments.global, rtd); + + let ortb2Config = bidConfig.ortb2Fragments.global; + expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1, setConfigUserObj2, rtdUserObj1]); + }); + + it('merges data without duplication', function() { + const userObj1 = { + name: 'www.dataprovider1.com', + ext: {segtax: 1}, + segment: [{id: '1776'} + ] + }; + + const userObj2 = { + ext: {segtax: 1}, + name: 'www.dataprovider2.com', + segment: [{id: '1914' + }] + }; + + const bidConfig = { + ortb2Fragments: + { + global: { + user: { + data: [userObj1, userObj2] + } + } + } + }; + + const rtd = { + ortb2: { + user: { + data: [userObj1] + } + } + }; + + addRealTimeData(bidConfig.ortb2Fragments.global, rtd); + + let ortb2Config = bidConfig.ortb2Fragments.global; + + expect(ortb2Config.user.data).to.deep.include.members([userObj1, userObj2]); + expect(bidConfig.ortb2Fragments.global.user.data).to.have.lengthOf(2); + }); + }); + + describe('Get BlueConic Real-Time Data', function() { + it('gets rtd from local storage cache', function() { + const rtdConfig = { + params: { + requestParams: { + publisherId: 'Publisher1', + coppa: true + }} + }; + + const bidConfig = {ortb2Fragments: {global: {}}}; + + const rtdUserObj1 = { + name: 'blueconic', + ext: {segtax: 1}, + segment: [{id: 'bf23d802-931d-4619-8266-ce9a6328aa2a'}], + bidId: '1234' + }; + + const cachedRtd = {ext: {segtax: 1}, 'segment': [{id: 'bf23d802-931d-4619-8266-ce9a6328aa2a'}], 'bidId': '1234'} + getDataFromLocalStorageStub.withArgs(RTD_LOCAL_NAME).returns(JSON.stringify(cachedRtd)); + + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + }); + }); +}); diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js index afb5f935621..5b51183ea6d 100644 --- a/test/spec/modules/boldwinBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -59,11 +59,12 @@ describe('BoldwinBidAdapter', function () { expect(data.gdpr).to.not.exist; expect(data.ccpa).to.not.exist; let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'bidFloor'); + expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'bidFloor', 'type'); expect(placement.placementId).to.equal('testBanner'); expect(placement.bidId).to.equal('23fhj33i987f'); expect(placement.adFormat).to.equal(BANNER); expect(placement.schain).to.be.an('object'); + expect(placement.type).to.exist.and.to.equal('publisher'); }); it('Returns valid data for mediatype video', function () { diff --git a/test/spec/modules/brandmetricsRtdProvider_spec.js b/test/spec/modules/brandmetricsRtdProvider_spec.js index 3cac5a3d559..879ec7e1c7a 100644 --- a/test/spec/modules/brandmetricsRtdProvider_spec.js +++ b/test/spec/modules/brandmetricsRtdProvider_spec.js @@ -124,12 +124,12 @@ describe('getBidRequestData', () => { }) it('should set targeting keys for specified bidders', () => { - brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => { - const bidderConfig = config.getBidderConfig() + const bidderOrtb2 = {}; + brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({ortb2Fragments: {bidder: bidderOrtb2}}, () => { const expected = VALID_CONFIG.params.bidders expected.forEach(exp => { - expect(bidderConfig[exp].ortb2.user.ext.data.mockTargetKey).to.equal('mockMeasurementId') + expect(bidderOrtb2[exp].user.ext.data.mockTargetKey).to.equal('mockMeasurementId') }) }, VALID_CONFIG); @@ -161,9 +161,9 @@ describe('getBidRequestData', () => { } }); - brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => {}, VALID_CONFIG); - const bidderConfig = config.getBidderConfig() - expect(Object.keys(bidderConfig).length).to.equal(0) + const bidderOrtb2 = {}; + brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({ortb2Fragments: {bidder: bidderOrtb2}}, () => {}, VALID_CONFIG); + expect(Object.keys(bidderOrtb2).length).to.equal(0) }); it('should use a default targeting key name if the brandmetrics- configuration does not include one', () => { @@ -179,13 +179,13 @@ describe('getBidRequestData', () => { } }); - brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({}, () => {}, VALID_CONFIG); + const bidderOrtb2 = {}; + brandmetricsRTD.brandmetricsSubmodule.getBidRequestData({ortb2Fragments: {bidder: bidderOrtb2}}, () => {}, VALID_CONFIG); - const bidderConfig = config.getBidderConfig() const expected = VALID_CONFIG.params.bidders expected.forEach(exp => { - expect(bidderConfig[exp].ortb2.user.ext.data.brandmetrics_survey).to.equal('mockMeasurementId') + expect(bidderOrtb2[exp].user.ext.data.brandmetrics_survey).to.equal('mockMeasurementId') }) }); }); diff --git a/test/spec/modules/bridgewellBidAdapter_spec.js b/test/spec/modules/bridgewellBidAdapter_spec.js index 8da82c71820..77818f34a62 100644 --- a/test/spec/modules/bridgewellBidAdapter_spec.js +++ b/test/spec/modules/bridgewellBidAdapter_spec.js @@ -7,7 +7,6 @@ const userId = { 'pubcid': '074864cb-3705-430e-9ff7-48ccf3c21b94', 'sharedid': {'id': '01F61MX53D786DSB2WYD38ZVM7', 'third': '01F61MX53D786DSB2WYD38ZVM7'}, 'uid2': {'id': 'eb33b0cb-8d35-1234-b9c0-1a31d4064777'}, - 'flocId': {'id': '12345', 'version': 'chrome.1.1'}, } describe('bridgewellBidAdapter', function () { @@ -142,7 +141,10 @@ describe('bridgewellBidAdapter', function () { it('should attach valid params to the tag', function () { const bidderRequest = { refererInfo: { - referer: 'https://www.bridgewell.com/' + page: 'https://www.bridgewell.com/', + legacy: { + referer: 'https://www.bridgewell.com/', + } } } const request = spec.buildRequests(bidRequests, bidderRequest); @@ -165,7 +167,10 @@ describe('bridgewellBidAdapter', function () { it('should attach valid params to the tag, part2', function() { const bidderRequest = { refererInfo: { - referer: 'https://www.bridgewell.com/' + page: 'https://www.bridgewell.com/', + legacy: { + referer: 'https://www.bridgewell.com/' + } } } const bidRequests2 = [ @@ -207,7 +212,10 @@ describe('bridgewellBidAdapter', function () { it('should attach validBidRequests to the tag', function () { const bidderRequest = { refererInfo: { - referer: 'https://www.bridgewell.com/' + page: 'https://www.bridgewell.com/', + legacy: { + referer: 'https://www.bridgewell.com/', + } } } diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js index b7d52c9f7d5..9354544ee5a 100644 --- a/test/spec/modules/brightcomBidAdapter_spec.js +++ b/test/spec/modules/brightcomBidAdapter_spec.js @@ -153,7 +153,8 @@ describe('brightcomBidAdapter', function() { gdprApplies: true }, refererInfo: { - referer: 'http://example.com/page.html', + page: 'http://example.com/page.html', + domain: 'example.com', } }; bidderRequest.bids = bidRequests; diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js index c36b48c5105..19483047827 100644 --- a/test/spec/modules/browsiRtdProvider_spec.js +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -3,6 +3,7 @@ import {makeSlot} from '../integration/faker/googletag.js'; import * as utils from '../../../src/utils' import * as events from '../../../src/events'; import * as sinon from 'sinon'; +import {sendPageviewEvent} from '../../../modules/browsiRtdProvider.js'; describe('browsi Real time data sub module', function () { const conf = { @@ -160,7 +161,19 @@ describe('browsi Real time data sub module', function () { }) }) - describe('should emit billable event', function () { + describe('should emit ad request billable event', function () { + before(() => { + const data = { + p: { + 'adUnit1': {ps: {0: 0.234}}, + 'adUnit2': {ps: {0: 0.134}}}, + kn: 'bv', + pmd: undefined, + bet: 'AD_REQUEST' + }; + browsiRTD.setData(data); + }) + beforeEach(() => { eventsEmitSpy.resetHistory(); }) @@ -232,4 +245,30 @@ describe('browsi Real time data sub module', function () { expect(callArguments).to.eql(expectedCall); }) }) + + describe('should emit pageveiw billable event', function () { + beforeEach(() => { + eventsEmitSpy.resetHistory(); + }) + it('should send event if type is correct', function () { + sendPageviewEvent('PAGEVIEW') + + const expectedCall = { + vendor: 'browsi', + type: 'pageview', + } + + expect(eventsEmitSpy.callCount).to.equal(1); + const callArguments = eventsEmitSpy.getCalls()[0].args[1]; + // billing id is random, we can't check its value + delete callArguments['billingId']; + expect(callArguments).to.eql(expectedCall); + }) + it('should not send event if type is incorrect', function () { + sendPageviewEvent('AD_REQUEST'); + sendPageviewEvent('INACTIVE'); + sendPageviewEvent(undefined); + expect(eventsEmitSpy.callCount).to.equal(0); + }) + }) }); diff --git a/test/spec/modules/byDataAnalyticsAdapter_spec.js b/test/spec/modules/byDataAnalyticsAdapter_spec.js index 90b4e1d53a6..1346284695c 100644 --- a/test/spec/modules/byDataAnalyticsAdapter_spec.js +++ b/test/spec/modules/byDataAnalyticsAdapter_spec.js @@ -9,18 +9,19 @@ const initOptions = { logFrequency: 1, }; let userData = { - userId: '5da77-ec87-277b-8e7a5', - client_id: 'asc00000', - plateform_name: 'Macintosh', - os_version: 10.157, - browser_name: 'Chrome', - browser_version: 92.04515107, - screen_size: { - width: 1440, - height: 900 + 'uid': '271a8-2b86-f4a4-f59bc', + 'cid': 'asc00000', + 'pid': 'www.letsrun.com', + 'os': 'Macintosh', + 'osv': 10.157, + 'br': 'Chrome', + 'brv': 103, + 'ss': { + 'width': 1792, + 'height': 1120 }, - device_type: 'Desktop', - time_zone: 'Asia/Calcutta' + 'de': 'Desktop', + 'tz': 'Asia/Calcutta' }; let bidTimeoutArgs = [{ auctionId, @@ -39,6 +40,18 @@ let noBidArgs = { src: 'client', transactionId: 'c8ee3914-1ee0-4ce6-9126-748d5692188c' } +let bidWonArgs = { + auctionId, + adUnitCode: 'div-gpt-ad-mrec1', + size: '300x250', + requestId: '15c86b6c10d3746', + bidder: 'appnexus', + timeToRespond: 114, + currency: 'USD', + mediaType: 'display', + cpm: 0.50 +} + let auctionEndArgs = { adUnitCodes: ['div-gpt-ad-mrec1'], adUnits: [{ @@ -74,21 +87,54 @@ let auctionEndArgs = { }] } let expectedDataArgs = { - visitor_data: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1ZGE3Ny1lYzg3LTI3N2ItOGU3YTUiLCJjbGllbnRfaWQiOiJhc2MwMDAwMCIsInBsYXRlZm9ybV9uYW1lIjoiTWFjaW50b3NoIiwib3NfdmVyc2lvbiI6MTAuMTU3LCJicm93c2VyX25hbWUiOiJDaHJvbWUiLCJicm93c2VyX3ZlcnNpb24iOjkyLjA0NTE1MTA3LCJzY3JlZW5fc2l6ZSI6eyJ3aWR0aCI6MTQ0MCwiaGVpZ2h0Ijo5MDB9LCJkZXZpY2VfdHlwZSI6IkRlc2t0b3AiLCJ0aW1lX3pvbmUiOiJBc2lhL0NhbGN1dHRhIn0.jNKjsb3Q-ZjkVMcbss_dQFOmu_GdkGqd7t9MbRmqlG4YEMorcJHhUVmUuPi-9pKvC9_t4XlgjED90UieCvdxCQ', - auction_id: auctionId, - auction_start: 1627973484504, + visitor_data: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIyNzFhOC0yYjg2LWY0YTQtZjU5YmMiLCJjaWQiOiJhc2MwMDAwMCIsInBpZCI6Ind3dy5sZXRzcnVuLmNvbSIsIm9zIjoiTWFjaW50b3NoIiwib3N2IjoxMC4xNTcsImJyIjoiQ2hyb21lIiwiYnJ2IjoxMDMsInNzIjp7IndpZHRoIjoxNzkyLCJoZWlnaHQiOjExMjB9LCJkZSI6IkRlc2t0b3AiLCJ0eiI6IkFzaWEvQ2FsY3V0dGEifQ.Oj3qnh--t06XO-foVmrMJCGqFfOBed09A-f7LZX5rtfBf4w1_RNRZ4F3on4TMPLonSa7GgzbcEfJS9G_amnleQ', + aid: auctionId, + as: 1627973484504, auctionData: [ { - 'adUnit': 'div-gpt-ad-mrec1', - 'size': '300x250', - 'media_type': 'display', - 'bids_bidder': 'appnexus', - 'bids_bid_id': '14480e9832f2d2b' + au: 'div-gpt-ad-mrec1', + auc: 'div-gpt-ad-mrec1', + aus: '300x250', + bidadv: 'appnexus', + bid: '14480e9832f2d2b', + inb: 0, + ito: 0, + ipwb: 0, + iwb: 0, + mt: 'display', }, { - 'adUnit': 'div-gpt-ad-mrec1', - 'size': '250x250', - 'media_type': 'display', - 'bids_bidder': 'appnexus', - 'bids_bid_id': '14480e9832f2d2b' + au: 'div-gpt-ad-mrec1', + auc: 'div-gpt-ad-mrec1', + aus: '250x250', + bidadv: 'appnexus', + bid: '14480e9832f2d2b', + inb: 0, + ito: 0, + ipwb: 0, + iwb: 0, + mt: 'display', + }] +} +let expectedBidWonArgs = { + visitor_data: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIyNzFhOC0yYjg2LWY0YTQtZjU5YmMiLCJjaWQiOiJhc2MwMDAwMCIsInBpZCI6Ind3dy5sZXRzcnVuLmNvbSIsIm9zIjoiTWFjaW50b3NoIiwib3N2IjoxMC4xNTcsImJyIjoiQ2hyb21lIiwiYnJ2IjoxMDMsInNzIjp7IndpZHRoIjoxNzkyLCJoZWlnaHQiOjExMjB9LCJkZSI6IkRlc2t0b3AiLCJ0eiI6IkFzaWEvQ2FsY3V0dGEifQ.Oj3qnh--t06XO-foVmrMJCGqFfOBed09A-f7LZX5rtfBf4w1_RNRZ4F3on4TMPLonSa7GgzbcEfJS9G_amnleQ', + aid: auctionId, + as: '', + auctionData: [{ + au: 'div-gpt-ad-mrec1', + auc: 'div-gpt-ad-mrec1', + aus: '300x250', + bid: '15c86b6c10d3746', + bidadv: 'appnexus', + br_pb_mg: 0.50, + br_tr: 114, + bradv: 'appnexus', + brid: '15c86b6c10d3746', + brs: '300x250', + cur: 'USD', + inb: 0, + ito: 0, + ipwb: 1, + iwb: 1, + mt: 'display', }] } @@ -130,10 +176,14 @@ describe('byData Analytics Adapter ', () => { it('sends and formatted auction data ', function () { events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgs); events.emit(constants.EVENTS.NO_BID, noBidArgs); + events.emit(constants.EVENTS.BID_WON, bidWonArgs) var userToken = ascAdapter.getVisitorData(userData); var newAuData = ascAdapter.dataProcess(auctionEndArgs); + var newBwData = ascAdapter.getBidWonData(bidWonArgs); newAuData['visitor_data'] = userToken; + newBwData['visitor_data'] = userToken; expect(newAuData).to.deep.equal(expectedDataArgs); + expect(newBwData).to.deep.equal(expectedBidWonArgs); }); }); }); diff --git a/test/spec/modules/c1xBidAdapter_spec.js b/test/spec/modules/c1xBidAdapter_spec.js new file mode 100644 index 00000000000..315680cba26 --- /dev/null +++ b/test/spec/modules/c1xBidAdapter_spec.js @@ -0,0 +1,189 @@ +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { c1xAdapter } from '../../../modules/c1xBidAdapter.js'; + +const ENDPOINT = 'https://hb-stg.c1exchange.com/ht'; +const BIDDER_CODE = 'c1x'; + +describe('C1XAdapter', () => { + const adapter = newBidder(c1xAdapter); + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + describe('isBidRequestValid', () => { + let bid = { + 'bidder': BIDDER_CODE, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'params': { + 'placementId': 'div-gpt-ad-1654594619717-0' + } + }; + it('should return true when required params found', function () { + expect(c1xAdapter.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when placementId not passed correctly', function () { + bid.params.placementId = undefined; + expect(c1xAdapter.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(c1xAdapter.isBidRequestValid(bid)).to.equal(false); + }); + }); + describe('buildRequests', () => { + let bidRequests = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'placementId': 'div-gpt-ad-1654594619717-0', + 'dealId': '1233' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250], [300, 600] + ] + } + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + const parseRequest = (data) => { + const parsedData = '{"' + data.replace(/=|&/g, (foundChar) => { + if (foundChar == '=') return '":"'; + else if (foundChar == '&') return '","'; + }) + '"}' + return parsedData; + }; + it('sends bid request to ENDPOINT via GET', () => { + const request = c1xAdapter.buildRequests(bidRequests); + expect(request[0].url).to.contain(ENDPOINT); + expect(request[0].method).to.equal('GET'); + }); + it('should generate correct bid Id tag', () => { + const request = c1xAdapter.buildRequests(bidRequests)[0]; + expect(request.bids[0].adUnitCode).to.equal('adunit-code'); + expect(request.bids[0].bidId).to.equal('30b31c1838de1e'); + }); + it('should convert params to proper form and attach to request', () => { + const request = c1xAdapter.buildRequests(bidRequests)[0]; + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.adunits).to.equal('1'); + expect(payloadObj.a1s).to.equal('300x250,300x600'); + expect(payloadObj.a1).to.equal('adunit-code'); + expect(payloadObj.a1d).to.equal('1233'); + }); + it('should convert floor price to proper form and attach to request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + 'params': { + 'placementId': 'div-gpt-ad-1654594619717-0', + 'dealId': '1233', + 'floorPriceMap': { + '300x250': 4.35 + } + } + }); + const request = c1xAdapter.buildRequests([bidRequest])[0]; + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.a1p).to.equal('4.35'); + }); + it('should convert pageurl to proper form and attach to request', () => { + let bidRequest = Object.assign({}, + bidRequests[0], + { + 'params': { + 'placementId': 'div-gpt-ad-1654594619717-0', + 'dealId': '1233', + 'pageurl': 'http://c1exchange.com/' + } + }); + + let bidderRequest = { + 'bidderCode': 'c1x' + } + bidderRequest.bids = bidRequests; + const request = c1xAdapter.buildRequests([bidRequest], bidderRequest)[0]; + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj.pageurl).to.equal('http://c1exchange.com/'); + }); + + it('should convert GDPR Consent to proper form and attach to request', () => { + let consentString = 'BOP2gFWOQIFovABABAENBGAAAAAAMw'; + let bidderRequest = { + 'bidderCode': 'c1x', + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true + } + } + bidderRequest.bids = bidRequests; + + const request = c1xAdapter.buildRequests(bidRequests, bidderRequest)[0]; + const originalPayload = parseRequest(request.data); + const payloadObj = JSON.parse(originalPayload); + expect(payloadObj['consent_string']).to.equal('BOP2gFWOQIFovABABAENBGAAAAAAMw'); + expect(payloadObj['consent_required']).to.equal('true'); + }); + }); + + describe('interpretResponse', () => { + let response = { + 'bid': true, + 'cpm': 1.5, + 'ad': '', + 'width': 300, + 'height': 250, + 'crid': '8888', + 'adId': 'c1x-test', + 'bidType': 'GROSS_BID' + }; + it('should get correct bid response', () => { + let expectedResponse = [ + { + width: 300, + height: 250, + cpm: 1.5, + ad: '', + creativeId: '8888', + currency: 'USD', + ttl: 300, + netRevenue: false, + requestId: 'yyyy' + } + ]; + let bidderRequest = {}; + bidderRequest.bids = [ + { + adUnitCode: 'c1x-test', + bidId: 'yyyy' + } + ]; + let result = c1xAdapter.interpretResponse({ body: [response] }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + it('handles nobid responses', () => { + let response = { + bid: false, + adId: 'c1x-test' + }; + let bidderRequest = {}; + let result = c1xAdapter.interpretResponse({ body: [response] }, bidderRequest); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/cleanioRtdProvider_spec.js b/test/spec/modules/cleanioRtdProvider_spec.js index 47c4b1b4961..8453b633906 100644 --- a/test/spec/modules/cleanioRtdProvider_spec.js +++ b/test/spec/modules/cleanioRtdProvider_spec.js @@ -1,5 +1,7 @@ import * as utils from '../../../src/utils.js'; import * as hook from '../../../src/hook.js' +import * as events from '../../../src/events.js'; +import CONSTANTS from '../../../src/constants.json'; import { __TEST__ } from '../../../modules/cleanioRtdProvider.js'; @@ -184,5 +186,26 @@ describe('clean.io RTD module', function () { onBidResponseEvent(fakeBidResponse3, {}, {}); ensurePrependToBidResponse(fakeBidResponse3); }); + + it('should send billable event per bid won event', function () { + const { init } = getModule(); + expect(init({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'full' } }, {})).to.equal(true); + + const eventCounter = { registerCleanioBillingEvent: function() {} }; + sinon.spy(eventCounter, 'registerCleanioBillingEvent'); + + events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, (evt) => { + if (evt.vendor === 'clean.io') { + eventCounter.registerCleanioBillingEvent() + } + }); + + events.emit(CONSTANTS.EVENTS.BID_WON, {}); + events.emit(CONSTANTS.EVENTS.BID_WON, {}); + events.emit(CONSTANTS.EVENTS.BID_WON, {}); + events.emit(CONSTANTS.EVENTS.BID_WON, {}); + + sinon.assert.callCount(eventCounter.registerCleanioBillingEvent, 4); + }); }); }); diff --git a/test/spec/modules/cleanmedianetBidAdapter_spec.js b/test/spec/modules/cleanmedianetBidAdapter_spec.js index c2eea6f32d7..8c2ac34350b 100644 --- a/test/spec/modules/cleanmedianetBidAdapter_spec.js +++ b/test/spec/modules/cleanmedianetBidAdapter_spec.js @@ -85,7 +85,7 @@ describe('CleanmedianetAdapter', function () { }, sizes: [[300, 250], [300, 600]], transactionId: 'a123456789', - refererInfo: { referer: 'https://examplereferer.com' }, + refererInfo: { referer: 'https://examplereferer.com', domain: 'examplereferer.com' }, gdprConsent: { consentString: 'some string', gdprApplies: true @@ -114,11 +114,16 @@ describe('CleanmedianetAdapter', function () { it('builds request correctly', function() { let bidRequest2 = utils.deepClone(bidRequest); - bidRequest2.refererInfo.referer = 'https://www.test.com/page.html'; + Object.assign(bidRequest2.refererInfo, { + page: 'https://www.test.com/page.html', + domain: 'test.com', + ref: 'https://referer.com' + }) + let response = spec.buildRequests([bidRequest], bidRequest2)[0]; - expect(response.data.site.domain).to.equal('www.test.com'); + expect(response.data.site.domain).to.equal('test.com'); expect(response.data.site.page).to.equal('https://www.test.com/page.html'); - expect(response.data.site.ref).to.equal('https://www.test.com/page.html'); + expect(response.data.site.ref).to.equal('https://referer.com'); expect(response.data.imp.length).to.equal(1); expect(response.data.imp[0].id).to.equal(bidRequest.transactionId); expect(response.data.imp[0].instl).to.equal(0); diff --git a/test/spec/modules/codefuelBidAdapter_spec.js b/test/spec/modules/codefuelBidAdapter_spec.js index a2549012d84..354cbe63ffa 100644 --- a/test/spec/modules/codefuelBidAdapter_spec.js +++ b/test/spec/modules/codefuelBidAdapter_spec.js @@ -139,7 +139,8 @@ describe('Codefuel Adapter', function () { timeout: 500, auctionId: '12043683-3254-4f74-8934-f941b085579e', refererInfo: { - referer: 'https://example.com/', + page: 'https://example.com/', + domain: 'example.com' } } diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index da050b3af90..f32a0bf4ebe 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -118,7 +118,6 @@ describe('ColossussspAdapter', function () { ...bid, params: { placement_id: 0, - traffic: 'video', }, mediaTypes: { video: { diff --git a/test/spec/modules/concertAnalyticsAdapter_spec.js b/test/spec/modules/concertAnalyticsAdapter_spec.js index b0aad2f3156..d130aea6043 100644 --- a/test/spec/modules/concertAnalyticsAdapter_spec.js +++ b/test/spec/modules/concertAnalyticsAdapter_spec.js @@ -48,7 +48,8 @@ describe('ConcertAnalyticsAdapter', function() { sandbox.spy(concertAnalytics, 'track'); fireBidEvents(events); - sandbox.assert.callCount(concertAnalytics.track, 5); + // 5 Concert events + 1 Clean.io event + sandbox.assert.callCount(concertAnalytics.track, 6); }); it('should report data for BID_RESPONSE, BID_WON events', function() { diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 1b869d51bde..2f9eda0ca7c 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -8,7 +8,15 @@ describe('ConcertAdapter', function () { let bidRequest; let bidResponse; + afterEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + concert: { + storageAllowed: true + } + }; bidRequests = [ { bidder: 'concert', @@ -25,7 +33,7 @@ describe('ConcertAdapter', function () { bidRequest = { refererInfo: { - referer: 'https://www.google.com' + page: 'https://www.google.com' }, uspConsent: '1YYY', gdprConsent: {} diff --git a/test/spec/modules/connectadBidAdapter_spec.js b/test/spec/modules/connectadBidAdapter_spec.js index 657bc432d06..7a70c4bacdb 100644 --- a/test/spec/modules/connectadBidAdapter_spec.js +++ b/test/spec/modules/connectadBidAdapter_spec.js @@ -255,14 +255,17 @@ describe('ConnectAd Adapter', function () { const bidRequest = Object.assign({}, bidRequests[0]) const bidderRequ = { refererInfo: { - referer: 'https://connectad.io/page.html', - reachedTop: true, - numIframes: 2, - stack: [ - 'https://connectad.io/page.html', - 'https://connectad.io/iframe1.html', - 'https://connectad.io/iframe2.html' - ] + page: 'https://connectad.io/page.html', + legacy: { + referer: 'https://connectad.io/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'https://connectad.io/page.html', + 'https://connectad.io/iframe1.html', + 'https://connectad.io/iframe2.html' + ] + } } } const request = spec.buildRequests([bidRequest], bidderRequ); diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index a2b1e22ed35..94a0ba92813 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -23,8 +23,27 @@ function createIFrameMarker() { } describe('consentManagement', function () { + it('should be enabled by default', () => { + expect(uspDataHandler.enabled).to.be.true; + }); + it('should respect configuration set after activation', () => { + setConsentConfig({ + usp: { + cmpApi: 'static', + consentData: { + getUSPData: { + uspString: '1YYY' + } + } + } + }); + expect(uspDataHandler.getConsentData()).to.equal('1YYY'); + }) + describe('setConsentConfig tests:', function () { describe('empty setConsentConfig value', function () { + before(resetConsentData); + beforeEach(function () { sinon.stub(utils, 'logInfo'); sinon.stub(utils, 'logWarn'); @@ -37,11 +56,11 @@ describe('consentManagement', function () { resetConsentData(); }); - it('should not run if no config', function () { + it('should run with defaults if no config', function () { setConsentConfig({}); - expect(consentAPI).to.be.undefined; - expect(consentTimeout).to.be.undefined; - sinon.assert.callCount(utils.logWarn, 1); + expect(consentAPI).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(50); + sinon.assert.callCount(utils.logInfo, 3); }); it('should use system default values', function () { @@ -51,11 +70,11 @@ describe('consentManagement', function () { sinon.assert.callCount(utils.logInfo, 3); }); - it('should exit the consent manager if config.usp is not an object', function() { + it('should not exit the consent manager if config.usp is not an object', function() { setConsentConfig({}); - expect(consentAPI).to.be.undefined; - sinon.assert.calledOnce(utils.logWarn); - sinon.assert.notCalled(utils.logInfo); + expect(consentAPI).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(50); + sinon.assert.callCount(utils.logInfo, 3); }); it('should not produce any USP metadata', function() { @@ -66,16 +85,16 @@ describe('consentManagement', function () { it('should exit the consent manager if only config.gdpr is an object', function() { setConsentConfig({ gdpr: { cmpApi: 'iab' } }); - expect(consentAPI).to.be.undefined; - sinon.assert.calledOnce(utils.logWarn); - sinon.assert.notCalled(utils.logInfo); + expect(consentAPI).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(50); + sinon.assert.callCount(utils.logInfo, 3); }); it('should exit consentManagementUsp module if config is "undefined"', function() { setConsentConfig(undefined); - expect(consentAPI).to.be.undefined; - sinon.assert.calledOnce(utils.logWarn); - sinon.assert.notCalled(utils.logInfo); + expect(consentAPI).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(50); + sinon.assert.callCount(utils.logInfo, 3); }); it('should immediately start looking up consent data', () => { diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index b0cd0197f8b..47a2e2ab3d9 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -1,4 +1,4 @@ -import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, allowAuction, staticConsentData, gdprScope } from 'modules/consentManagement.js'; +import { setConsentConfig, requestBidsHook, resetConsentData, userCMP, consentTimeout, staticConsentData, gdprScope } from 'modules/consentManagement.js'; import { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; @@ -69,17 +69,12 @@ describe('consentManagement', function () { let allConfig = { cmpApi: 'iab', timeout: 7500, - allowAuctionWithoutConsent: false, defaultGdprScope: true }; setConsentConfig(allConfig); expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(7500); - expect(allowAuction).to.deep.equal({ - value: false, - definedInConfig: true - }); expect(gdprScope).to.be.true; }); @@ -125,16 +120,11 @@ describe('consentManagement', function () { setConsentConfig({ cmpApi: 'iab', timeout: 3333, - allowAuctionWithoutConsent: false, gdpr: false }); expect(userCMP).to.be.equal('iab'); expect(consentTimeout).to.be.equal(3333); - expect(allowAuction).to.deep.equal({ - value: false, - definedInConfig: true - }); expect(gdprScope).to.be.equal(false); }); @@ -148,63 +138,11 @@ describe('consentManagement', function () { afterEach(() => { config.resetConfig(); }); - it('results in user settings overriding system defaults for v1 spec', () => { - let staticConfig = { - cmpApi: 'static', - timeout: 7500, - allowAuctionWithoutConsent: false, - consentData: { - getConsentData: { - 'gdprApplies': true, - 'hasGlobalScope': false, - 'consentData': 'BOOgjO9OOgjO9APABAENAi-AAAAWd7_______9____7_9uz_Gv_r_ff_3nW0739P1A_r_Oz_rm_-zzV44_lpQQRCEA' - }, - getVendorConsents: { - 'metadata': 'BOOgjO9OOgjO9APABAENAi-AAAAWd7_______9____7_9uz_Gv_r_ff_3nW0739P1A_r_Oz_rm_-zzV44_lpQQRCEA', - 'gdprApplies': true, - 'hasGlobalScope': false, - 'isEU': true, - 'cookieVersion': 1, - 'created': '2018-05-29T07:45:48.522Z', - 'lastUpdated': '2018-05-29T07:45:48.522Z', - 'cmpId': 15, - 'cmpVersion': 1, - 'consentLanguage': 'EN', - 'vendorListVersion': 34, - 'maxVendorId': 359, - 'purposeConsents': { - '1': true, - '2': true, - '3': true, - '4': true, - '5': true - }, - 'vendorConsents': { - '1': true, - '2': true, - '3': true, - '4': true, - '5': false - } - } - } - }; - - setConsentConfig(staticConfig); - expect(userCMP).to.be.equal('static'); - expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used - expect(allowAuction).to.deep.equal({ - value: false, - definedInConfig: true - }); - expect(staticConsentData).to.be.equal(staticConfig.consentData); - }); it('results in user settings overriding system defaults for v2 spec', () => { let staticConfig = { cmpApi: 'static', timeout: 7500, - allowAuctionWithoutConsent: false, consentData: { getTCData: { 'tcString': 'COuqj-POu90rDBcBkBENAZCgAPzAAAPAACiQFwwBAABAA1ADEAbQC4YAYAAgAxAG0A', @@ -276,10 +214,6 @@ describe('consentManagement', function () { setConsentConfig(staticConfig); expect(userCMP).to.be.equal('static'); expect(consentTimeout).to.be.equal(0); // should always return without a timeout when config is used - expect(allowAuction).to.deep.equal({ - value: false, - definedInConfig: true - }); expect(gdprScope).to.be.equal(false); const consent = gdprDataHandler.getConsentData(); expect(consent.consentString).to.eql(staticConfig.consentData.getTCData.tcString); @@ -290,16 +224,9 @@ describe('consentManagement', function () { }); describe('requestBidsHook tests:', function () { - let goodConfigWithCancelAuction = { + let goodConfig = { cmpApi: 'iab', timeout: 7500, - allowAuctionWithoutConsent: false - }; - - let goodConfigWithAllowAuction = { - cmpApi: 'iab', - timeout: 7500, - allowAuctionWithoutConsent: true }; const staticConfig = { @@ -312,10 +239,8 @@ describe('consentManagement', function () { let didHookReturn; - afterEach(function () { - gdprDataHandler.consentData = null; - resetConsentData(); - }); + beforeEach(resetConsentData); + after(resetConsentData) describe('error checks:', function () { beforeEach(function () { @@ -328,7 +253,6 @@ describe('consentManagement', function () { utils.logWarn.restore(); utils.logError.restore(); config.resetConfig(); - resetConsentData(); }); it('should throw a warning and return to hooked function when an unknown CMP framework ID is used', function () { @@ -356,7 +280,7 @@ describe('consentManagement', function () { }) it('should throw proper errors when CMP is not found', function () { - setConsentConfig(goodConfigWithCancelAuction); + setConsentConfig(goodConfig); requestBidsHook(() => { didHookReturn = true; @@ -378,41 +302,59 @@ describe('consentManagement', function () { return gdprDataHandler.promise.then(() => { expect(ran).to.be.true; }); + }); + + it('should continue the auction immediately, without consent data, if timeout is 0', (done) => { + setConsentConfig({ + cmpApi: 'iab', + timeout: 0, + defaultGdprScope: true + }); + window.__tcfapi = function () {}; + try { + requestBidsHook(() => { + const consent = gdprDataHandler.getConsentData(); + expect(consent.gdprApplies).to.be.true; + expect(consent.consentString).to.be.undefined; + done(); + }, {}) + } finally { + delete window.__tcfapi; + } }) }); describe('already known consentData:', function () { let cmpStub = sinon.stub(); + function mockCMP(cmpResponse) { + return function(...args) { + args[2](Object.assign({eventStatus: 'tcloaded'}, cmpResponse), true); + } + } + beforeEach(function () { didHookReturn = false; - window.__cmp = function () { }; + window.__tcfapi = function () { }; }); afterEach(function () { config.resetConfig(); cmpStub.restore(); - delete window.__cmp; + delete window.__tcfapi; resetConsentData(); }); it('should bypass CMP and simply use previously stored consentData', function () { let testConsentData = { gdprApplies: true, - consentData: 'xyz' + tcString: 'xyz', }; - cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { - args[2](testConsentData); - }); - setConsentConfig(goodConfigWithAllowAuction); + cmpStub = sinon.stub(window, '__tcfapi').callsFake(mockCMP(testConsentData)); + setConsentConfig(goodConfig); requestBidsHook(() => { }, {}); - cmpStub.restore(); - - // reset the stub to ensure it wasn't called during the second round of calls - cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { - args[2](testConsentData); - }); + cmpStub.reset(); requestBidsHook(() => { didHookReturn = true; @@ -420,7 +362,7 @@ describe('consentManagement', function () { let consent = gdprDataHandler.getConsentData(); expect(didHookReturn).to.be.true; - expect(consent.consentString).to.equal(testConsentData.consentData); + expect(consent.consentString).to.equal(testConsentData.tcString); expect(consent.gdprApplies).to.be.true; sinon.assert.notCalled(cmpStub); }); @@ -428,12 +370,10 @@ describe('consentManagement', function () { it('should not set consent.gdprApplies to true if defaultGdprScope is true', function () { let testConsentData = { gdprApplies: false, - consentData: 'xyz' + tcString: 'xyz', }; - cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { - args[2](testConsentData); - }); + cmpStub = sinon.stub(window, '__tcfapi').callsFake(mockCMP(testConsentData)); setConsentConfig({ cmpApi: 'iab', @@ -486,7 +426,7 @@ describe('consentManagement', function () { function testIFramedPage(testName, messageFormatString, tarConsentString, ver) { it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, (done) => { stringifyResponse = messageFormatString; - setConsentConfig(goodConfigWithAllowAuction); + setConsentConfig(goodConfig); requestBidsHook(() => { let consent = gdprDataHandler.getConsentData(); sinon.assert.notCalled(utils.logError); @@ -510,93 +450,6 @@ describe('consentManagement', function () { resetConsentData(); }); - describe('v1 CMP workflow for safeframe page', function () { - let registerStub = sinon.stub(); - let ifrSf = null; - beforeEach(function () { - didHookReturn = false; - window.$sf = { - ext: { - register: function () { }, - cmp: function () { } - } - }; - ifrSf = createIFrameMarker('__cmpLocator'); - }); - - afterEach(function () { - delete window.$sf; - registerStub.restore(); - document.body.removeChild(ifrSf); - }); - - it('should return the consent data from a safeframe callback', function () { - let testConsentData = { - data: { - msgName: 'cmpReturn', - vendorConsents: { - metadata: 'abc123def', - gdprApplies: true - }, - vendorConsentData: { - consentData: 'abc123def', - gdprApplies: true - } - } - }; - registerStub = sinon.stub(window.$sf.ext, 'register').callsFake((...args) => { - args[2](testConsentData.data.msgName, testConsentData.data); - }); - - setConsentConfig(goodConfigWithAllowAuction); - requestBidsHook(() => { - didHookReturn = true; - }, { adUnits: [{ sizes: [[300, 250]] }] }); - let consent = gdprDataHandler.getConsentData(); - - sinon.assert.notCalled(utils.logWarn); - sinon.assert.notCalled(utils.logError); - expect(didHookReturn).to.be.true; - expect(consent.consentString).to.equal('abc123def'); - expect(consent.gdprApplies).to.be.true; - expect(consent.apiVersion).to.equal(1); - }); - }); - - describe('v1 CMP workflow for iframe pages', function () { - stringifyResponse = false; - let ifr1 = null; - - beforeEach(function () { - ifr1 = createIFrameMarker('__cmpLocator'); - cmpPostMessageCb = creatCmpMessageHandler('__cmp', { - consentData: 'encoded_consent_data_via_post_message', - gdprApplies: true, - }); - window.addEventListener('message', cmpPostMessageCb, false); - }); - - afterEach(function () { - delete window.__cmp; // deletes the local copy made by the postMessage CMP call function - document.body.removeChild(ifr1); - window.removeEventListener('message', cmpPostMessageCb); - }); - - // Run tests with JSON response and String response - // from CMP window postMessage listener. - testIFramedPage('with/JSON response', false, 'encoded_consent_data_via_post_message', 1); - testIFramedPage('with/String response', true, 'encoded_consent_data_via_post_message', 1); - - it('should contain correct V1 CMP definition', (done) => { - setConsentConfig(goodConfigWithAllowAuction); - requestBidsHook(() => { - const nbArguments = window.__cmp.toString().split('\n')[0].split(', ').length; - expect(nbArguments).to.equal(3); - done(); - }, {}); - }); - }); - describe('v2 CMP workflow for iframe pages:', function () { stringifyResponse = false; let ifr2 = null; @@ -622,7 +475,7 @@ describe('consentManagement', function () { testIFramedPage('with/String response', true, 'abc12345234', 2); it('should contain correct v2 CMP definition', (done) => { - setConsentConfig(goodConfigWithAllowAuction); + setConsentConfig(goodConfig); requestBidsHook(() => { const nbArguments = window.__tcfapi.toString().split('\n')[0].split(', ').length; expect(nbArguments).to.equal(4); @@ -649,40 +502,6 @@ describe('consentManagement', function () { resetConsentData(); }); - describe('v1 CMP workflow for normal pages:', function () { - beforeEach(function () { - window.__cmp = function () { }; - }); - - afterEach(function () { - delete window.__cmp; - }); - - it('performs lookup check and stores consentData for a valid existing user', function () { - let testConsentData = { - gdprApplies: true, - consentData: 'BOJy+UqOJy+UqABAB+AAAAAZ+A==' - }; - cmpStub = sinon.stub(window, '__cmp').callsFake((...args) => { - args[2](testConsentData); - }); - - setConsentConfig(goodConfigWithAllowAuction); - - requestBidsHook(() => { - didHookReturn = true; - }, {}); - let consent = gdprDataHandler.getConsentData(); - - sinon.assert.notCalled(utils.logWarn); - sinon.assert.notCalled(utils.logError); - expect(didHookReturn).to.be.true; - expect(consent.consentString).to.equal(testConsentData.consentData); - expect(consent.gdprApplies).to.be.true; - expect(consent.apiVersion).to.equal(1); - }); - }); - describe('v2 CMP workflow for normal pages:', function () { beforeEach(function() { window.__tcfapi = function () { }; @@ -703,7 +522,7 @@ describe('consentManagement', function () { args[2](testConsentData, true); }); - setConsentConfig(goodConfigWithAllowAuction); + setConsentConfig(goodConfig); requestBidsHook(() => { didHookReturn = true; @@ -730,7 +549,7 @@ describe('consentManagement', function () { args[2](testConsentData, true); }); - setConsentConfig(goodConfigWithAllowAuction); + setConsentConfig(goodConfig); requestBidsHook(() => { didHookReturn = true; @@ -755,7 +574,7 @@ describe('consentManagement', function () { args[2](testConsentData, true); }); - setConsentConfig(goodConfigWithAllowAuction); + setConsentConfig(goodConfig); requestBidsHook(() => { didHookReturn = true; @@ -769,7 +588,7 @@ describe('consentManagement', function () { expect(consent.apiVersion).to.equal(2); }); - it('throws an error when processCmpData check fails + does not call requestBids callbcack even when allowAuction is true', function () { + it('throws an error when processCmpData check fails + does not call requestBids callback', function () { let testConsentData = {}; let bidsBackHandlerReturn = false; @@ -777,9 +596,9 @@ describe('consentManagement', function () { args[2](testConsentData); }); - setConsentConfig(goodConfigWithAllowAuction); + setConsentConfig(goodConfig); - sinon.assert.calledOnce(utils.logWarn); + sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); [utils.logWarn, utils.logError].forEach((stub) => stub.reset()); @@ -797,25 +616,84 @@ describe('consentManagement', function () { expect(gdprDataHandler.ready).to.be.true; }); - it('allows the auction when CMP is unresponsive', (done) => { - setConsentConfig({ - cmpApi: 'iab', - timeout: 10, - defaultGdprScope: true + describe('when proper consent is not available', () => { + let tcfStub; + + function runAuction() { + setConsentConfig({ + cmpApi: 'iab', + timeout: 10, + defaultGdprScope: true + }); + return new Promise((resolve, reject) => { + requestBidsHook(() => { + didHookReturn = true; + }, {}); + setTimeout(() => didHookReturn ? resolve() : reject(new Error('Auction did not run')), 20); + }) + } + + function mockTcfEvent(tcdata) { + tcfStub.callsFake((api, version, cb) => { + if (api === 'addEventListener' && version === 2) { + // eslint-disable-next-line standard/no-callback-literal + cb(tcdata, true) + } + }); + } + + beforeEach(() => { + tcfStub = sinon.stub(window, '__tcfapi'); }); - requestBidsHook(() => { - didHookReturn = true; - }, {}); + afterEach(() => { + tcfStub.restore(); + }) + + it('should continue auction with null consent when CMP is unresponsive', () => { + return runAuction().then(() => { + const consent = gdprDataHandler.getConsentData(); + expect(consent.gdprApplies).to.be.true; + expect(consent.consentString).to.be.undefined; + expect(gdprDataHandler.ready).to.be.true; + }); + }); - setTimeout(() => { - expect(didHookReturn).to.be.true; - const consent = gdprDataHandler.getConsentData(); - expect(consent.gdprApplies).to.be.true; - expect(consent.consentString).to.be.undefined; - expect(gdprDataHandler.ready).to.be.true; - done(); - }, 20); + it('should use consent provided by events other than tcloaded', () => { + mockTcfEvent({ + eventStatus: 'cmpuishown', + tcString: 'mock-consent-string', + vendorData: {} + }); + return runAuction().then(() => { + const consent = gdprDataHandler.getConsentData(); + expect(consent.gdprApplies).to.be.true; + expect(consent.consentString).to.equal('mock-consent-string'); + expect(consent.vendorData.vendorData).to.eql({}); + expect(gdprDataHandler.ready).to.be.true; + }); + }); + + Object.entries({ + 'null': null, + 'empty': '', + 'undefined': undefined + }).forEach(([t, cs]) => { + // some CMPs appear to reply with an empty consent string in 'cmpuishown' - make sure we don't use that + it(`should NOT use "default" consent if string is ${t}`, () => { + mockTcfEvent({ + eventStatus: 'cmpuishown', + tcString: cs, + vendorData: {random: 'junk'} + }); + return runAuction().then(() => { + const consent = gdprDataHandler.getConsentData(); + expect(consent.gdprApplies).to.be.true; + expect(consent.consentString).to.be.undefined; + expect(consent.vendorData).to.be.undefined; + }); + }) + }); }); it('It still considers it a valid cmp response if gdprApplies is not a boolean', function () { diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index b70cd6fe631..d1b310624a6 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -1,6 +1,9 @@ import {expect} from 'chai'; import {spec} from 'modules/consumableBidAdapter.js'; import {createBid} from 'src/bidfactory.js'; +import {config} from 'src/config.js'; +import {deepClone} from 'src/utils.js'; +import { createEidsArray } from 'modules/userId/eids.js'; const ENDPOINT = 'https://e.serverbid.com/api/v2'; const SMARTSYNC_CALLBACK = 'serverbidCallBids'; @@ -30,6 +33,22 @@ const BIDDER_REQUEST_1 = { transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' } ], + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, + { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 2 + }, + ] + }, gdprConsent: { consentString: 'consent-test', gdprApplies: false @@ -110,6 +129,53 @@ const BIDDER_REQUEST_2 = { } }; +const BIDDER_REQUEST_VIDEO = { + bidderCode: 'consumable', + auctionId: 'a4713c32-3762-4798-b342-4ab810ca770d', + bidderRequestId: '109f2a181342a9', + bidRequest: [ + { + bidder: 'consumable', + params: { + networkId: 9969, + siteId: 730181, + unitId: 123456, + unitName: 'cnsmbl-unit' + }, + placementCode: 'div-gpt-ad-1487778092495-0', + mediaTypes: { + video: { + playerSize: [188, 106], + context: 'instream', + mimes: ['application/javascript', 'application/x-mpegurl', 'video/3gpp', 'video/mp4', 'video/mpeg', 'video/ogg', 'video/webm', 'video/x-m4v', 'video/x-ms-asf', 'video/x-ms-wmv', 'video/x-msvideo'], + minduration: 0, + maxduration: 120, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + api: [1, 2], + linearity: 1 + } + }, + bidId: '6202d555b2f94537', + bidderRequestId: '109f2a181342a9', + auctionId: 'a4713c32-3762-4798-b342-4ab810ca770d' + } + ], + gdprConsent: { + consentString: 'consent-test', + gdprApplies: true + }, + refererInfo: { + referer: 'http://example.com/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'http://example.com/page.html', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ] + } +}; + const BIDDER_REQUEST_EMPTY = { bidderCode: 'consumable', auctionId: 'b06458ef-4fe5-4a0b-a61b-bccbcedb7b11', @@ -240,6 +306,58 @@ const AD_SERVER_RESPONSE_2 = { } }; +const AD_SERVER_RESPONSE_VIDEO_1 = { + 'headers': null, + 'body': { + 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'pixels': [{ 'type': 'image', 'url': '//sync.serverbid.com/ss/' }], + 'decisions': { + '6202d555b2f94537': { + 'adId': 3866158402, + 'creativeId': 'C1-somo-test-video', + 'width': 640, + 'height': 480, + 'pricing': { + 'clearPrice': 1.58 + }, + 'vastUrl': 'https://x.serverbid.com/rtb/v?auc=217c051d06b011ed9cbc72b17f01ec03&sc=1.575&s=22&a=9dcab16d340d664310c2135a76989fe946a9d46e5d5f24ff5e2f17bffbb7704a43638bd3f600951e&n=9&r=0&t=1658158906595', + 'uuid': 'f1e7287514ce11ed9c1de2b3ba87449a', + 'bidderName': 'consumable', + 'adomain': ['consumabletv.com'], + 'cats': ['IAB3-1'], + 'mediaType': 'video', + 'networkId': 1 + } + } + } +}; + +const AD_SERVER_RESPONSE_VIDEO_2 = { + 'headers': null, + 'body': { + 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, + 'pixels': [{ 'type': 'image', 'url': '//sync.serverbid.com/ss/' }], + 'decisions': { + '6202d555b2f94537': { + 'adId': 3866158402, + 'creativeId': 'C1-somo-test-video', + 'width': 640, + 'height': 480, + 'pricing': { + 'clearPrice': 1.58 + }, + 'vastXml': '', + 'uuid': 'f1e7287514ce11ed9c1de2b3ba87449a', + 'bidderName': 'consumable', + 'adomain': ['consumabletv.com'], + 'cats': ['IAB3-1'], + 'mediaType': 'video', + 'networkId': 1 + } + } + } +}; + const BUILD_REQUESTS_OUTPUT = { method: 'POST', url: 'https://e.serverbid.com/api/v2', @@ -248,6 +366,14 @@ const BUILD_REQUESTS_OUTPUT = { bidderRequest: BIDDER_REQUEST_2 }; +const BUILD_REQUESTS_VIDEO_OUTPUT = { + method: 'POST', + url: 'https://e.serverbid.com/api/v2', + data: '', + bidRequest: BIDDER_REQUEST_VIDEO.bidRequest, + bidderRequest: BIDDER_REQUEST_VIDEO +}; + describe('Consumable BidAdapter', function () { let adapter = spec; @@ -332,7 +458,63 @@ describe('Consumable BidAdapter', function () { let request = spec.buildRequests(BIDDER_REQUEST_1.bidRequest, BIDDER_REQUEST_1); expect(request.bidderRequest).to.equal(BIDDER_REQUEST_1); - }) + }); + + it('should contain schain if it exists in the bidRequest', function () { + let request = spec.buildRequests(BIDDER_REQUEST_1.bidRequest, BIDDER_REQUEST_1); + let data = JSON.parse(request.data); + + expect(data.schain).to.deep.equal(BIDDER_REQUEST_1.schain) + }); + + it('should not contain schain if it does not exist in the bidRequest', function () { + let request = spec.buildRequests(BIDDER_REQUEST_2.bidRequest, BIDDER_REQUEST_2); + let data = JSON.parse(request.data); + + expect(data.schain).to.be.undefined; + }); + + it('should contain coppa if configured', function () { + config.setConfig({ coppa: true }); + let request = spec.buildRequests(BIDDER_REQUEST_1.bidRequest, BIDDER_REQUEST_1); + let data = JSON.parse(request.data); + + expect(data.coppa).to.be.true; + }); + + it('should not contain coppa if not configured', function () { + config.setConfig({ coppa: false }); + let request = spec.buildRequests(BIDDER_REQUEST_1.bidRequest, BIDDER_REQUEST_1); + let data = JSON.parse(request.data); + + expect(data.coppa).to.be.undefined; + }); + + it('should contain video object for video requests', function () { + let request = spec.buildRequests(BIDDER_REQUEST_VIDEO.bidRequest, BIDDER_REQUEST_VIDEO); + let data = JSON.parse(request.data); + + expect(data.placements[0].video).to.deep.equal(BIDDER_REQUEST_VIDEO.bidRequest[0].mediaTypes.video); + }); + + it('sets bidfloor param if present', function () { + let bidderRequest1 = deepClone(BIDDER_REQUEST_1); + let bidderRequest2 = deepClone(BIDDER_REQUEST_2); + bidderRequest1.bidRequest[0].params.bidFloor = 0.05; + bidderRequest2.bidRequest[0].getFloor = function() { + return { + currency: 'USD', + floor: 0.15 + } + }; + let request1 = spec.buildRequests(bidderRequest1.bidRequest, BIDDER_REQUEST_1); + let data1 = JSON.parse(request1.data); + let request2 = spec.buildRequests(bidderRequest2.bidRequest, BIDDER_REQUEST_2); + let data2 = JSON.parse(request2.data); + + expect(data1.placements[0].bidfloor).to.equal(0.05); + expect(data2.placements[0].bidfloor).to.equal(0.15); + }); }); describe('interpretResponse validation', function () { it('response should have valid bidderCode', function () { @@ -372,6 +554,31 @@ describe('Consumable BidAdapter', function () { }); }); + it('registers video bids with vastUrl', function () { + let bids = spec.interpretResponse(AD_SERVER_RESPONSE_VIDEO_1, BUILD_REQUESTS_VIDEO_OUTPUT); + + bids.forEach(b => { + expect(b.mediaType).to.equal('video'); + expect(b.meta).to.have.property('mediaType', 'video'); + expect(b.vastUrl).to.equal('https://x.serverbid.com/rtb/v?auc=217c051d06b011ed9cbc72b17f01ec03&sc=1.575&s=22&a=9dcab16d340d664310c2135a76989fe946a9d46e5d5f24ff5e2f17bffbb7704a43638bd3f600951e&n=9&r=0&t=1658158906595'); + expect(b.vastXml).to.be.undefined; + expect(b.videoCacheKey).to.equal('f1e7287514ce11ed9c1de2b3ba87449a'); + }); + }) + + it('registers video bids with vastXml', function () { + let bids = spec.interpretResponse(AD_SERVER_RESPONSE_VIDEO_2, BUILD_REQUESTS_VIDEO_OUTPUT); + + bids.forEach(b => { + expect(b.mediaType).to.equal('video'); + expect(b.meta).to.have.property('mediaType', 'video'); + expect(b.vastXml).to.equal(''); + expect(b.vastUrl).to.be.undefined; + expect(b.ad).to.equal(''); + expect(b.videoCacheKey).to.equal('f1e7287514ce11ed9c1de2b3ba87449a'); + }); + }) + it('handles nobid responses', function () { let EMPTY_RESP = Object.assign({}, AD_SERVER_RESPONSE, {'body': {'decisions': null}}) let bids = spec.interpretResponse(EMPTY_RESP, BUILD_REQUESTS_OUTPUT); @@ -425,4 +632,76 @@ describe('Consumable BidAdapter', function () { expect(opts.length).to.equal(1); }); }); + describe('unifiedId from userId module', function() { + let sandbox, bidderRequest; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + bidderRequest = deepClone(BIDDER_REQUEST_1); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('Request should have unifiedId config params', function() { + bidderRequest.bidRequest[0].userId = {}; + bidderRequest.bidRequest[0].userId.tdid = 'TTD_ID'; + bidderRequest.bidRequest[0].userIdAsEids = createEidsArray(bidderRequest.bidRequest[0].userId); + let request = spec.buildRequests(bidderRequest.bidRequest, BIDDER_REQUEST_1); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'TTD_ID', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); + + it('Request should have adsrvrOrgId from UserId Module if config and userId module both have TTD ID', function() { + sandbox.stub(config, 'getConfig').callsFake((key) => { + var config = { + adsrvrOrgId: { + 'TDID': 'TTD_ID_FROM_CONFIG', + 'TDID_LOOKUP': 'TRUE', + 'TDID_CREATED_AT': '2022-06-21T09:47:00' + } + }; + return config[key]; + }); + bidderRequest.bidRequest[0].userId = {}; + bidderRequest.bidRequest[0].userId.tdid = 'TTD_ID'; + bidderRequest.bidRequest[0].userIdAsEids = createEidsArray(bidderRequest.bidRequest[0].userId); + let request = spec.buildRequests(bidderRequest.bidRequest, BIDDER_REQUEST_1); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'TTD_ID', + 'atype': 1, + 'ext': { + 'rtiPartner': 'TDID' + } + }] + }]); + }); + + it('Request should NOT have adsrvrOrgId params if userId is NOT object', function() { + let request = spec.buildRequests(bidderRequest.bidRequest, BIDDER_REQUEST_1); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + }); + + it('Request should NOT have adsrvrOrgId params if userId.tdid is NOT string', function() { + bidderRequest.bidRequest[0].userId = { + tdid: 1234 + }; + let request = spec.buildRequests(bidderRequest.bidRequest, BIDDER_REQUEST_1); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal(undefined); + }); + }); }); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index ebc9879bb84..c63dc8f9c3b 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -254,7 +254,7 @@ describe('Conversant adapter tests', function() { const page = 'http://test.com?a=b&c=123'; const bidderRequest = { refererInfo: { - referer: page + page: page } }; const request = spec.buildRequests(bidRequests, bidderRequest); @@ -392,9 +392,10 @@ describe('Conversant adapter tests', function() { }); it('Verify first party data', () => { - const bidderRequest = {refererInfo: {referer: 'http://test.com?a=b&c=123'}}; - const cfg = {ortb2: {site: {content: {series: 'MySeries', season: 'MySeason', episode: 3, title: 'MyTitle'}}}}; - config.setConfig(cfg); + const bidderRequest = { + refererInfo: {page: 'http://test.com?a=b&c=123'}, + ortb2: {site: {content: {series: 'MySeries', season: 'MySeason', episode: 3, title: 'MyTitle'}}} + }; const request = spec.buildRequests(bidRequests, bidderRequest); const payload = request.data; expect(payload.site).to.have.property('content'); @@ -402,11 +403,10 @@ describe('Conversant adapter tests', function() { expect(payload.site.content).to.have.property('season'); expect(payload.site.content).to.have.property('episode'); expect(payload.site.content).to.have.property('title'); - config.resetConfig(); }); it('Verify supply chain data', () => { - const bidderRequest = {refererInfo: {referer: 'http://test.com?a=b&c=123'}}; + const bidderRequest = {refererInfo: {page: 'http://test.com?a=b&c=123'}}; const schain = {complete: 1, ver: '1.0', nodes: [{asi: 'bidderA.com', sid: '00001', hp: 1}]}; const bidsWithSchain = bidRequests.map((bid) => { return Object.assign({ @@ -421,12 +421,12 @@ describe('Conversant adapter tests', function() { it('Verify override url', function() { const testUrl = 'https://someurl?name=value'; - const request = spec.buildRequests([{params: {white_label_url: testUrl}}]); + const request = spec.buildRequests([{params: {white_label_url: testUrl}}], {}); expect(request.url).to.equal(testUrl); }); it('Verify interpretResponse', function() { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, {}); const response = spec.interpretResponse(bidResponses, request); expect(response).to.be.an('array').with.lengthOf(4); @@ -489,7 +489,7 @@ describe('Conversant adapter tests', function() { Object.assign(unit, {crumbs: {pubcid: 12345}}); }); // construct http post payload - const payload = spec.buildRequests(requests).data; + const payload = spec.buildRequests(requests, {}).data; expect(payload).to.have.deep.nested.property('user.ext.fpc', 12345); expect(payload).to.not.have.nested.property('user.ext.eids'); }); @@ -504,7 +504,7 @@ describe('Conversant adapter tests', function() { Object.assign(unit, {userIdAsEids: createEidsArray(unit.userId)}); }); // construct http post payload - const payload = spec.buildRequests(requests).data; + const payload = spec.buildRequests(requests, {}).data; expect(payload).to.have.deep.nested.property('user.ext.fpc', 67890); expect(payload).to.not.have.nested.property('user.ext.eids'); }); @@ -579,7 +579,7 @@ describe('Conversant adapter tests', function() { Object.assign(unit, {userIdAsEids: createEidsArray(unit.userId)}); }); // construct http post payload - const payload = spec.buildRequests(requests).data; + const payload = spec.buildRequests(requests, {}).data; expect(payload).to.have.deep.nested.property('user.ext.eids', [ {source: 'adserver.org', uids: [{id: '223344', atype: 1, ext: {rtiPartner: 'TDID'}}]}, {source: 'liveramp.com', uids: [{id: '334455', atype: 3}]} @@ -603,7 +603,15 @@ describe('Conversant adapter tests', function() { return (new Date(Date.now() + timeout * 60 * 60 * 24 * 1000)).toUTCString(); } + beforeEach(() => { + $$PREBID_GLOBAL$$.bidderSettings = { + conversant: { + storageAllowed: true + } + }; + }); afterEach(() => { + $$PREBID_GLOBAL$$.bidderSettings = {}; cleanUp(ID_NAME); cleanUp(CUSTOM_ID_NAME); }); @@ -616,7 +624,7 @@ describe('Conversant adapter tests', function() { storage.setCookie(ID_NAME, '12345', expStr(TIMEOUT)); // construct http post payload - const payload = spec.buildRequests(requests).data; + const payload = spec.buildRequests(requests, {}).data; expect(payload).to.have.deep.nested.property('user.ext.fpc', '12345'); }); @@ -629,7 +637,7 @@ describe('Conversant adapter tests', function() { storage.setCookie(CUSTOM_ID_NAME, '12345', expStr(TIMEOUT)); // construct http post payload - const payload = spec.buildRequests(requests).data; + const payload = spec.buildRequests(requests, {}).data; expect(payload).to.have.deep.nested.property('user.ext.fpc', '12345'); }); @@ -642,7 +650,7 @@ describe('Conversant adapter tests', function() { storage.setDataInLocalStorage(ID_NAME, 'abcde'); // construct http post payload - const payload = spec.buildRequests(requests).data; + const payload = spec.buildRequests(requests, {}).data; expect(payload).to.have.deep.nested.property('user.ext.fpc', 'abcde'); }); @@ -655,7 +663,7 @@ describe('Conversant adapter tests', function() { storage.setDataInLocalStorage(ID_NAME, 'fghijk'); // construct http post payload - const payload = spec.buildRequests(requests).data; + const payload = spec.buildRequests(requests, {}).data; expect(payload).to.have.deep.nested.property('user.ext.fpc', 'fghijk'); }); @@ -668,7 +676,7 @@ describe('Conversant adapter tests', function() { storage.setDataInLocalStorage(ID_NAME, 'lmnopq'); // construct http post payload - const payload = spec.buildRequests(requests).data; + const payload = spec.buildRequests(requests, {}).data; expect(payload).to.not.have.deep.nested.property('user.ext.fpc'); }); @@ -682,7 +690,7 @@ describe('Conversant adapter tests', function() { storage.setDataInLocalStorage(CUSTOM_ID_NAME, 'fghijk'); // construct http post payload - const payload = spec.buildRequests(requests).data; + const payload = spec.buildRequests(requests, {}).data; expect(payload).to.have.deep.nested.property('user.ext.fpc', 'fghijk'); }); }); @@ -702,7 +710,7 @@ describe('Conversant adapter tests', function() { }; }; - const payload = spec.buildRequests(bidRequest).data; + const payload = spec.buildRequests(bidRequest, {}).data; expect(payload.imp[0]).to.have.property('bidfloor', 3.21); }); @@ -715,7 +723,7 @@ describe('Conversant adapter tests', function() { }; bidRequest[0].params.bidfloor = 0.6; - const payload = spec.buildRequests(bidRequest).data; + const payload = spec.buildRequests(bidRequest, {}).data; expect(payload.imp[0]).to.have.property('bidfloor', 0.6); }); @@ -727,7 +735,7 @@ describe('Conversant adapter tests', function() { }; }; - const payload = spec.buildRequests(bidRequest).data; + const payload = spec.buildRequests(bidRequest, {}).data; expect(payload.imp[0]).to.have.property('bidfloor', 0); }); @@ -739,7 +747,7 @@ describe('Conversant adapter tests', function() { }; }; - const payload = spec.buildRequests(bidRequest).data; + const payload = spec.buildRequests(bidRequest, {}).data; expect(payload.imp[0]).to.have.property('bidfloor', 0); }); @@ -748,14 +756,14 @@ describe('Conversant adapter tests', function() { return {}; }; - const payload = spec.buildRequests(bidRequest).data; + const payload = spec.buildRequests(bidRequest, {}).data; expect(payload.imp[0]).to.have.property('bidfloor', 0); }); it('undefined floor result', function() { bidRequest[0].getFloor = () => {}; - const payload = spec.buildRequests(bidRequest).data; + const payload = spec.buildRequests(bidRequest, {}).data; expect(payload.imp[0]).to.have.property('bidfloor', 0); }); }); diff --git a/test/spec/modules/cpexIdSystem_spec.js b/test/spec/modules/cpexIdSystem_spec.js index ee203e6c83a..e1320d82a6a 100644 --- a/test/spec/modules/cpexIdSystem_spec.js +++ b/test/spec/modules/cpexIdSystem_spec.js @@ -16,21 +16,21 @@ describe('cpexId module', function () { describe('getId()', function () { it('should return the uid when it exists in cookie', function () { - getCookieStub.withArgs('caid').returns('cpexIdTest'); + getCookieStub.withArgs('czaid').returns('cpexIdTest'); const id = cpexIdSubmodule.getId(); - expect(id).to.be.deep.equal({ cpexId: 'cpexIdTest' }); + expect(id).to.be.deep.equal({ id: 'cpexIdTest' }); }); cookieTestCasesForEmpty.forEach(testCase => it('should not return the uid when it doesnt exist in cookie', function () { - getCookieStub.withArgs('caid').returns(testCase); + getCookieStub.withArgs('czaid').returns(testCase); const id = cpexIdSubmodule.getId(); - expect(id).to.be.deep.equal({ cpexId: null }); + expect(id).to.be.undefined; })); }); describe('decode()', function () { it('should return the uid when it exists in cookie', function () { - getCookieStub.withArgs('caid').returns('cpexIdTest'); + getCookieStub.withArgs('czaid').returns('cpexIdTest'); const decoded = cpexIdSubmodule.decode(); expect(decoded).to.be.deep.equal({ cpexId: 'cpexIdTest' }); }); diff --git a/test/spec/modules/craftBidAdapter_spec.js b/test/spec/modules/craftBidAdapter_spec.js index 3f4bc977016..dfdbebde738 100644 --- a/test/spec/modules/craftBidAdapter_spec.js +++ b/test/spec/modules/craftBidAdapter_spec.js @@ -14,11 +14,17 @@ describe('craftAdapter', function () { describe('isBidRequestValid', function () { before(function() { + $$PREBID_GLOBAL$$.bidderSettings = { + craft: { + storageAllowed: true + } + }; this.windowContext = window.context; window.context = null; }); after(function() { + $$PREBID_GLOBAL$$.bidderSettings = {}; window.context = this.windowContext; }); let bid = { @@ -60,6 +66,16 @@ describe('craftAdapter', function () { }); describe('buildRequests', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + craft: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); let bidRequests = [{ bidder: 'craft', params: { @@ -75,7 +91,7 @@ describe('craftAdapter', function () { }]; let bidderRequest = { refererInfo: { - referer: 'https://www.gacraft.jp/publish/craft-prebid-example.html' + topmostLocation: 'https://www.gacraft.jp/publish/craft-prebid-example.html' } }; it('sends bid request to ENDPOINT via POST', function () { diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index d15691e20b1..7af5f5d77a2 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { tryGetCriteoFastBid, spec, + storage, PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, canFastBid, getFastBidUrl, FAST_BID_VERSION_CURRENT @@ -9,13 +10,20 @@ import { import { createBid } from 'src/bidfactory.js'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; +import * as refererDetection from 'src/refererDetection.js'; import { config } from '../../../src/config.js'; -import { NATIVE, VIDEO } from '../../../src/mediaTypes.js'; +import * as storageManager from 'src/storageManager.js'; +import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; describe('The Criteo bidding adapter', function () { let utilsMock, sandbox; beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + criteo: { + storageAllowed: true + } + }; // Remove FastBid to avoid side effects localStorage.removeItem('criteo_fast_bid'); utilsMock = sinon.mock(utils); @@ -24,11 +32,189 @@ describe('The Criteo bidding adapter', function () { }); afterEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; global.Criteo = undefined; utilsMock.restore(); sandbox.restore(); }); + describe('getUserSyncs', function () { + const syncOptionsIframeEnabled = { + iframeEnabled: true + }; + + const expectedHash = { + cw: true, + lsw: true, + origin: 'criteoPrebidAdapter', + requestId: '123456', + tld: 'www.abc.com', + topUrl: 'www.abc.com', + version: '$prebid.version$'.replace(/\./g, '_'), + }; + + let randomStub, + getConfigStub, + getRefererInfoStub, + cookiesAreEnabledStub, + localStorageIsEnabledStub, + getCookieStub, + getDataFromLocalStorageStub; + + beforeEach(function () { + getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('criteo.fastBidVersion').returns('none'); + + randomStub = sinon.stub(Math, 'random'); + randomStub.returns(123456); + + getRefererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); + getRefererInfoStub.returns({ + domain: 'www.abc.com' + }); + + cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); + cookiesAreEnabledStub.returns(true); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + localStorageIsEnabledStub.returns(true); + + getCookieStub = sinon.stub(storage, 'getCookie') + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + randomStub.restore(); + getConfigStub.restore(); + getRefererInfoStub.restore(); + cookiesAreEnabledStub.restore(); + localStorageIsEnabledStub.restore(); + getCookieStub.restore(); + getDataFromLocalStorageStub.restore(); + }); + + it('should not trigger sync if publisher is using fast bid', function () { + getConfigStub.withArgs('criteo.fastBidVersion').returns('latest'); + + const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, undefined, undefined); + + expect(userSyncs).to.eql([]); + }); + + it('should not trigger sync if publisher did not enable iframe based syncs', function () { + const userSyncs = spec.getUserSyncs({ + iframeEnabled: false + }, undefined, undefined, undefined); + + expect(userSyncs).to.eql([]); + }); + + it('should not trigger sync if purpose one is not granted', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'ABC', + vendorData: { + purpose: { + consents: { + 1: false + } + } + } + }; + const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, gdprConsent, undefined); + + expect(userSyncs).to.eql([]); + }); + + it('forwards ids from cookies', function () { + const cookieData = { + 'cto_bundle': 'a', + 'cto_sid': 'b', + 'cto_lwid': 'c', + 'cto_idcpy': 'd', + 'cto_optout': 'e' + }; + + const expectedHashWithCookieData = { + ...expectedHash, + ...{ + bundle: cookieData['cto_bundle'], + localWebId: cookieData['cto_lwid'], + secureIdCookie: cookieData['cto_sid'], + uid: cookieData['cto_idcpy'], + optoutCookie: cookieData['cto_optout'] + } + }; + + getCookieStub.callsFake(cookieName => cookieData[cookieName]); + + const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, undefined, undefined); + + expect(userSyncs).to.eql([{ + type: 'iframe', + url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com#${JSON.stringify(expectedHashWithCookieData, Object.keys(expectedHashWithCookieData).sort()).replace(/"/g, '%22')}` + }]); + }); + + it('forwards ids from local storage', function () { + const localStorageData = { + 'cto_bundle': 'a', + 'cto_sid': 'b', + 'cto_lwid': 'c', + 'cto_idcpy': 'd', + 'cto_optout': 'e' + }; + + const expectedHashWithLocalStorageData = { + ...expectedHash, + ...{ + bundle: localStorageData['cto_bundle'], + localWebId: localStorageData['cto_lwid'], + secureIdCookie: localStorageData['cto_sid'], + uid: localStorageData['cto_idcpy'], + optoutCookie: localStorageData['cto_optout'] + } + }; + + getDataFromLocalStorageStub.callsFake(localStorageName => localStorageData[localStorageName]); + + const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, undefined, undefined); + + expect(userSyncs).to.eql([{ + type: 'iframe', + url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com#${JSON.stringify(expectedHashWithLocalStorageData, Object.keys(expectedHashWithLocalStorageData).sort()).replace(/"/g, '%22')}` + }]); + }); + + it('forwards gdpr data', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'ABC', + vendorData: { + purpose: { + consents: { + 1: true + } + } + } + }; + const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, gdprConsent, undefined); + + expect(userSyncs).to.eql([{ + type: 'iframe', + url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com&gdpr=1&gdpr_consent=ABC#${JSON.stringify(expectedHash).replace(/"/g, '%22')}` + }]); + }); + + it('forwards usp data', function () { + const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, undefined, 'ABC'); + + expect(userSyncs).to.eql([{ + type: 'iframe', + url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com&us_privacy=ABC#${JSON.stringify(expectedHash).replace(/"/g, '%22')}` + }]); + }); + }); + describe('isBidRequestValid', function () { it('should return false when given an invalid bid', function () { const bid = { @@ -404,7 +590,8 @@ describe('The Criteo bidding adapter', function () { const refererUrl = 'https://criteo.com?pbt_debug=1&pbt_nolog=1'; const bidderRequest = { refererInfo: { - referer: refererUrl + page: refererUrl, + topmostLocation: refererUrl }, timeout: 3000, gdprConsent: { @@ -419,7 +606,15 @@ describe('The Criteo bidding adapter', function () { }, }; + let localStorageIsEnabledStub; + + this.beforeEach(function () { + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + localStorageIsEnabledStub.returns(true); + }); + afterEach(function () { + localStorageIsEnabledStub.restore(); config.resetConfig(); }); @@ -463,7 +658,7 @@ describe('The Criteo bidding adapter', function () { }, ]; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&im=1&debug=1&nolog=1/); + expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=1&im=1&debug=1&nolog=1/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; expect(ortbRequest.publisher.url).to.equal(refererUrl); @@ -512,7 +707,7 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].sizes[0]).to.equal('undefinedxundefined'); }); - it('should properly detect and get sizes of native sizeless banner', function () { + it('should properly detect and forward native flag', function () { const bidRequests = [ { mediaTypes: { @@ -527,11 +722,10 @@ describe('The Criteo bidding adapter', function () { ]; const request = spec.buildRequests(bidRequests, bidderRequest); const ortbRequest = request.data; - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); - expect(ortbRequest.slots[0].sizes[0]).to.equal('2x2'); + expect(ortbRequest.slots[0].native).to.equal(true); }); - it('should properly detect and get size of native sizeless banner', function () { + it('should properly detect and forward native flag', function () { const bidRequests = [ { mediaTypes: { @@ -546,14 +740,14 @@ describe('The Criteo bidding adapter', function () { ]; const request = spec.buildRequests(bidRequests, bidderRequest); const ortbRequest = request.data; - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); - expect(ortbRequest.slots[0].sizes[0]).to.equal('2x2'); + expect(ortbRequest.slots[0].native).to.equal(true); }); it('should properly build a networkId request', function () { const bidderRequest = { refererInfo: { - referer: refererUrl + page: refererUrl, + topmostLocation: refererUrl, }, timeout: 3000, gdprConsent: { @@ -600,7 +794,8 @@ describe('The Criteo bidding adapter', function () { it('should properly build a mixed request', function () { const bidderRequest = { refererInfo: { - referer: refererUrl + page: refererUrl, + topmostLocation: refererUrl, }, timeout: 3000 }; @@ -925,19 +1120,14 @@ describe('The Criteo bidding adapter', function () { }, ]; - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - }; - return utils.deepAccess(config, key); - }); - - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2: {} }); expect(request.data.publisher.ext).to.equal(undefined); expect(request.data.user.ext).to.equal(undefined); expect(request.data.slots[0].ext).to.equal(undefined); }); it('should properly build a request with criteo specific ad unit first party data', function () { + // TODO: this test does not do what it says const bidRequests = [ { bidder: 'criteo', @@ -957,13 +1147,7 @@ describe('The Criteo bidding adapter', function () { }, ]; - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - }; - return utils.deepAccess(config, key); - }); - - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2: {} }); expect(request.data.slots[0].ext).to.deep.equal({ bidfloor: 0.75, }); @@ -1012,17 +1196,12 @@ describe('The Criteo bidding adapter', function () { }, ]; - sandbox.stub(config, 'getConfig').callsFake(key => { - const config = { - ortb2: { - site: siteData, - user: userData - } - }; - return utils.deepAccess(config, key); - }); + const ortb2 = { + site: siteData, + user: userData + }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); expect(request.data.publisher.ext).to.deep.equal({ data: { pageType: 'article' } }); expect(request.data.user.ext).to.deep.equal({ data: { registered: true } }); expect(request.data.slots[0].ext).to.deep.equal({ @@ -1032,6 +1211,185 @@ describe('The Criteo bidding adapter', function () { } }); }); + + it('should properly build a request when coppa flag is true', function () { + const bidRequests = []; + const bidderRequest = {}; + config.setConfig({ coppa: true }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.regs.coppa).to.not.be.undefined; + expect(request.data.regs.coppa).to.equal(1); + }); + + it('should properly build a request when coppa flag is false', function () { + const bidRequests = []; + const bidderRequest = {}; + config.setConfig({ coppa: false }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.regs.coppa).to.not.be.undefined; + expect(request.data.regs.coppa).to.equal(0); + }); + + it('should properly build a request when coppa flag is not defined', function () { + const bidRequests = []; + const bidderRequest = {}; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.regs.coppa).to.be.undefined; + }); + + it('should properly build a banner request with floors', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + params: { + networkId: 456, + }, + + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else { + return {} + } + } + }, + ]; + const bidderRequest = {}; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].ext.floors).to.deep.equal({ + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + }); + }); + + it('should properly build a video request with several player sizes with floors', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + video: { + playerSize: [[300, 250], [728, 90]] + } + }, + params: { + networkId: 456, + }, + + getFloor: inputParams => { + if (inputParams.mediaType === VIDEO && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === VIDEO && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else { + return {} + } + } + }, + ]; + const bidderRequest = {}; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].ext.floors).to.deep.equal({ + 'video': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + }); + }); + + it('should properly build a multi format request with floors', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + }, + video: { + playerSize: [640, 480], + }, + native: {} + }, + params: { + networkId: 456, + }, + ortb2Imp: { + ext: { + data: { + someContextAttribute: 'abc' + } + } + }, + + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else if (inputParams.mediaType === VIDEO && inputParams.size[0] === 640 && inputParams.size[1] === 480) { + return { + currency: 'EUR', + floor: 3.2 + }; + } else if (inputParams.mediaType === NATIVE && inputParams.size === '*') { + return { + currency: 'YEN', + floor: 4.99 + }; + } else { + return {} + } + } + }, + ]; + const bidderRequest = {}; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].ext.data.someContextAttribute).to.deep.equal('abc'); + expect(request.data.slots[0].ext.floors).to.deep.equal({ + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + }, + 'video': { + '640x480': { 'currency': 'EUR', 'floor': 3.2 } + }, + 'native': { + '*': { 'currency': 'YEN', 'floor': 4.99 } + } + }); + }); }); describe('interpretResponse', function () { @@ -1104,7 +1462,6 @@ describe('The Criteo bidding adapter', function () { const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(1); expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].adId).to.equal('abc123'); expect(bids[0].cpm).to.equal(1.23); expect(bids[0].ad).to.equal('test-ad'); expect(bids[0].width).to.equal(728); @@ -1138,7 +1495,6 @@ describe('The Criteo bidding adapter', function () { const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(1); expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].adId).to.equal('abc123'); expect(bids[0].cpm).to.equal(1.23); expect(bids[0].vastUrl).to.equal('http://test-ad'); expect(bids[0].mediaType).to.equal(VIDEO); @@ -1195,7 +1551,6 @@ describe('The Criteo bidding adapter', function () { const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(1); expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].adId).to.equal('abc123'); expect(bids[0].cpm).to.equal(1.23); expect(bids[0].mediaType).to.equal(NATIVE); }); @@ -1314,40 +1669,6 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].height).to.equal(90); }); - it('should generate unique adIds if none are returned by the endpoint', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - width: 300, - height: 250, - }, { - impid: 'test-requestId', - cpm: 4.56, - creative: 'test-ad', - width: 728, - height: 90, - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - sizes: [[300, 250], [728, 90]], - params: { - networkId: 456, - } - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(2); - const prebidBids = bids.map(bid => Object.assign(createBid(CONSTANTS.STATUS.GOOD, request.bidRequests[0]), bid)); - expect(prebidBids[0].adId).to.not.equal(prebidBids[1].adId); - }); - [{ hasBidResponseLevelPafData: true, hasBidResponseBidLevelPafData: true, diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index d50eadebb55..76d5222c8b2 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -1,6 +1,6 @@ import { criteoIdSubmodule, storage } from 'modules/criteoIdSystem.js'; import * as utils from 'src/utils.js'; -import {server} from '../../mocks/xhr'; +import { server } from '../../mocks/xhr'; const pastDateString = new Date(0).toString() @@ -25,7 +25,7 @@ describe('CriteoId module', function () { setLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); removeFromLocalStorageStub = sinon.stub(storage, 'removeDataFromLocalStorage'); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); - parseUrlStub = sinon.stub(utils, 'parseUrl').returns({protocol: 'https', hostname: 'testdev.com'}) + parseUrlStub = sinon.stub(utils, 'parseUrl').returns({ protocol: 'https', hostname: 'testdev.com' }) triggerPixelStub = sinon.stub(utils, 'triggerPixel'); done(); }); @@ -64,20 +64,21 @@ describe('CriteoId module', function () { it('should call user sync url with the right params', function () { getCookieStub.withArgs('cto_bundle').returns('bundle'); + getCookieStub.withArgs('cto_dna_bundle').returns('info'); window.criteo_pubtag = {} let callBackSpy = sinon.spy(); let result = criteoIdSubmodule.getId(); result.callback(callBackSpy); - const expectedUrl = `https://gum.criteo.com/sid/json?origin=prebid&topUrl=https%3A%2F%2Ftestdev.com%2F&domain=testdev.com&bundle=bundle&cw=1&pbt=1&lsw=1`; + const expectedUrl = `https://gum.criteo.com/sid/json?origin=prebid&topUrl=https%3A%2F%2Ftestdev.com%2F&domain=testdev.com&bundle=bundle&info=info&cw=1&pbt=1&lsw=1`; let request = server.requests[0]; expect(request.url).to.be.eq(expectedUrl); request.respond( 200, - {'Content-Type': 'application/json'}, + { 'Content-Type': 'application/json' }, JSON.stringify({}) ); expect(callBackSpy.calledOnce).to.be.true; @@ -107,7 +108,7 @@ describe('CriteoId module', function () { let request = server.requests[0]; request.respond( 200, - {'Content-Type': 'application/json'}, + { 'Content-Type': 'application/json' }, JSON.stringify(response) ); @@ -142,6 +143,48 @@ describe('CriteoId module', function () { { consentData: undefined, expected: undefined } ]; + it('should call sync pixels if request by backend', function () { + const expirationTs = new Date(nowTimestamp + cookiesMaxAge).toString(); + + const result = criteoIdSubmodule.getId(); + result.callback((id) => { + + }); + + const response = { + pixels: [ + { + pixelUrl: 'pixelUrlWithBundle', + writeBundleInStorage: true, + bundlePropertyName: 'abc', + storageKeyName: 'cto_pixel_test' + }, + { + pixelUrl: 'pixelUrl' + } + ] + }; + + server.requests[0].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(response) + ); + + server.requests[1].respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ + abc: 'ok' + }) + ); + + expect(triggerPixelStub.called).to.be.true; + expect(setCookieStub.calledWith('cto_pixel_test', 'ok', expirationTs, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_pixel_test', 'ok', expirationTs, null, '.testdev.com')).to.be.true; + expect(setLocalStorageStub.calledWith('cto_pixel_test', 'ok')).to.be.true; + }); + gdprConsentTestCases.forEach(testCase => it('should call user sync url with the gdprConsent', function () { let callBackSpy = sinon.spy(); let result = criteoIdSubmodule.getId(undefined, testCase.consentData); @@ -156,7 +199,7 @@ describe('CriteoId module', function () { request.respond( 200, - {'Content-Type': 'application/json'}, + { 'Content-Type': 'application/json' }, JSON.stringify({}) ); diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index 928c252943c..6eb4f929a42 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -169,6 +169,28 @@ describe('currency', function () { expect(getGlobal().convertCurrency(1.0, 'USD', 'EUR')).to.equal(4); expect(getGlobal().convertCurrency(1.0, 'USD', 'JPY')).to.equal(200); }); + it('uses default rates until currency file is loaded', function () { + setConfig({ + adServerCurrency: 'USD', + defaultRates: { + USD: { + JPY: 100 + } + } + }); + + // Race condition where a bid is converted before the file has been loaded + expect(getGlobal().convertCurrency(1.0, 'USD', 'JPY')).to.equal(100); + + fakeCurrencyFileServer.respondWith(JSON.stringify({ + 'dataAsOf': '2017-04-25', + 'conversions': { + 'USD': { JPY: 200 } + } + })); + fakeCurrencyFileServer.respond(); + expect(getGlobal().convertCurrency(1.0, 'USD', 'JPY')).to.equal(200); + }); }); describe('bidder override', function () { it('allows setConfig to set bidder currency', function () { diff --git a/test/spec/modules/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js index b21e73e1561..f116b184b8c 100644 --- a/test/spec/modules/cwireBidAdapter_spec.js +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -120,6 +120,19 @@ describe('C-WIRE bid adapter', () => { bid01.params.pageId = '3320'; expect(spec.isBidRequestValid(bid01)).to.equal(false); }); + + it('should fail if cwcreative of type number', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + delete bid01.params.cwcreative; + bid01.params.cwcreative = 3320; + expect(spec.isBidRequestValid(bid01)).to.equal(false); + }); + + it('should pass with valid cwcreative of type string', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + bid01.params.cwcreative = 'i-am-a-string'; + expect(spec.isBidRequestValid(bid01)).to.equal(true); + }); }); describe('C-WIRE - buildRequests()', function () { @@ -131,7 +144,7 @@ describe('C-WIRE bid adapter', () => { } } }).withParams({ - cwcreative: 54321, + cwcreative: '54321', cwapikey: 'xxx-xxx-yyy-zzz-uuid', refgroups: 'group_1', }).build(); @@ -144,7 +157,7 @@ describe('C-WIRE bid adapter', () => { expect(requests.data.cwid).to.be.null; expect(requests.data.cwid).to.be.null; expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal(54321); + expect(requests.data.cwcreative).to.equal('54321'); expect(requests.data.cwapikey).to.equal('xxx-xxx-yyy-zzz-uuid'); expect(requests.data.refgroups[0]).to.equal('group_1'); }); @@ -159,7 +172,7 @@ describe('C-WIRE bid adapter', () => { } } }).withParams({ - cwcreative: 1234, + cwcreative: '1234', cwapikey: 'api_key_5', refgroups: 'group_5', }).build(); @@ -171,7 +184,7 @@ describe('C-WIRE bid adapter', () => { expect(requests.data.slots.length).to.equal(2); expect(requests.data.cwid).to.be.null; expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal(1234); + expect(requests.data.cwcreative).to.equal('1234'); expect(requests.data.cwapikey).to.equal('api_key_5'); expect(requests.data.refgroups[0]).to.equal('group_5'); }); @@ -179,14 +192,14 @@ describe('C-WIRE bid adapter', () => { it('creates a valid request - read debug params from first bid, ignore second', function () { const bid01 = new BidRequestBuilder() .withParams({ - cwcreative: 33, + cwcreative: '33', cwapikey: 'api_key_33', refgroups: 'group_33', }).build(); const bid02 = new BidRequestBuilder() .withParams({ - cwcreative: 1234, + cwcreative: '1234', cwapikey: 'api_key_5', refgroups: 'group_5', }).build(); @@ -198,7 +211,7 @@ describe('C-WIRE bid adapter', () => { expect(requests.data.slots.length).to.equal(2); expect(requests.data.cwid).to.be.null; expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal(33); + expect(requests.data.cwcreative).to.equal('33'); expect(requests.data.cwapikey).to.equal('api_key_33'); expect(requests.data.refgroups[0]).to.equal('group_33'); }); @@ -206,7 +219,7 @@ describe('C-WIRE bid adapter', () => { it('creates a valid request - read debug params from 3 different slots', function () { const bid01 = new BidRequestBuilder() .withParams({ - cwcreative: 33, + cwcreative: '33', }).build(); const bid02 = new BidRequestBuilder() @@ -225,7 +238,7 @@ describe('C-WIRE bid adapter', () => { expect(requests.data.slots.length).to.equal(3); expect(requests.data.cwid).to.be.null; expect(requests.data.cwid).to.be.null; - expect(requests.data.cwcreative).to.equal(33); + expect(requests.data.cwcreative).to.equal('33'); expect(requests.data.cwapikey).to.equal('api_key_5'); expect(requests.data.refgroups[0]).to.equal('group_5'); }); @@ -244,7 +257,7 @@ describe('C-WIRE bid adapter', () => { } } }).withParams({ - cwcreative: 54321, + cwcreative: '54321', cwapikey: 'xxx-xxx-yyy-zzz', refgroups: 'group_1', }).build(); @@ -257,7 +270,7 @@ describe('C-WIRE bid adapter', () => { expect(requests.data.cwid).to.be.null; expect(requests.data.cwid).to.be.null; expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); - expect(requests.data.cwcreative).to.equal(654321); + expect(requests.data.cwcreative).to.equal('654321'); expect(requests.data.cwapikey).to.equal('xxx-xxx-yyy-zzz'); expect(requests.data.refgroups[0]).to.equal('group_2'); }); diff --git a/test/spec/modules/dataController_spec.js b/test/spec/modules/dataController_spec.js new file mode 100644 index 00000000000..25f55047377 --- /dev/null +++ b/test/spec/modules/dataController_spec.js @@ -0,0 +1,210 @@ +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {filterBidData, init} from 'modules/dataControllerModule/index.js'; +import {startAuction} from 'src/prebid.js'; + +describe('data controller', function () { + let spyFn; + + beforeEach(function () { + spyFn = sinon.spy(); + }); + + afterEach(function () { + config.resetConfig(); + }); + + describe('data controller', function () { + let result; + let callbackFn; + let req; + + beforeEach(function () { + init(); + result = null; + req = { + 'adUnits': [{ + 'bids': [ + { + 'bidder': 'ix', + 'userId': { + 'id5id': { + 'uid': 'ID5*19RudTU8mWiRdKfG-0E9oyyJCdbgb8MvcaEtPAxM29QZYT5DW9r01vBozMD93UZy', + 'ext': { + 'linkType': 2 + } + } + }, + 'userIdAsEids': [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'ID5*UJzjz7J0FNIWPCp8fAmwGavBhGxnJ06V9umghosEVm4ZPjpn2iWahAoiPal59yKa', + 'atype': 1, + 'ext': { + 'linkType': 2 + } + } + ] + } + ], + + } + ] + }], + 'ortb2Fragments': { + 'bidder': { + 'ix': { + 'user': { + 'data': [ + { + 'name': 'permutive.com', + 'ext': { + 'segtax': 4 + }, + 'segment': [ + { + 'id': '777777' + }, + { + 'id': '888888' + } + ] + } + ] + } + } + } + } + }; + callbackFn = function (request) { + result = request; + }; + }); + + afterEach(function () { + config.resetConfig(); + startAuction.getHooks({hook: filterBidData}).remove(); + }); + + it('filterEIDwhenSDA for All SDA ', function () { + let dataControllerConfiguration = { + 'dataController': { + filterEIDwhenSDA: ['*'] + } + }; + config.setConfig(dataControllerConfiguration); + filterBidData(callbackFn, req); + expect(req.adUnits[0].bids[0].userIdAsEids).that.is.empty; + expect(req.adUnits[0].bids[0].userId).that.is.empty; + expect(req.ortb2Fragments.bidder.ix.user.ext.eids).that.is.empty; + expect(req.ortb2Fragments.global.user.ext.eids).that.is.empty; + }); + + it('filterEIDwhenSDA for available SAD permutive.com:4:777777 ', function () { + let dataControllerConfiguration = { + 'dataController': { + filterEIDwhenSDA: ['permutive.com:4:777777'] + } + + }; + config.setConfig(dataControllerConfiguration); + filterBidData(callbackFn, req); + expect(req.adUnits[0].bids[0].userIdAsEids).that.is.empty; + expect(req.adUnits[0].bids[0].userId).that.is.empty; + + expect(req.ortb2Fragments.bidder.ix.user.ext.eids).that.is.empty; + expect(req.ortb2Fragments.global.user.ext.eids).that.is.empty; + }); + + it('filterEIDwhenSDA for unavailable SAD test.com:4:9999 ', function () { + let dataControllerConfiguration = { + 'dataController': { + filterEIDwhenSDA: ['test.com:4:99999'] + } + }; + config.setConfig(dataControllerConfiguration); + filterBidData(callbackFn, req); + expect(req.adUnits[0].bids[0].userIdAsEids).that.is.not.empty; + expect(req.adUnits[0].bids[0].userId).that.is.not.empty; + }); + // Test for global + it('filterEIDwhenSDA for available global SAD test.com:4:777777 ', function () { + let dataControllerConfiguration = { + 'dataController': { + filterEIDwhenSDA: ['test.com:5:11111'] + } + + }; + config.setConfig(dataControllerConfiguration); + let globalObject = { + 'ortb2Fragments': { + 'global': { + 'user': { + 'yob': 1985, + 'gender': 'm', + 'keywords': 'a,b', + 'data': [ + { + 'name': 'test.com', + 'ext': { + 'segtax': 5 + }, + 'segment': [ + { + 'id': '11111' + }, + { + 'id': '22222' + } + ] + } + ] + } + } + } + }; + let globalRequest = Object.assign({}, req, globalObject); + filterBidData(callbackFn, globalRequest); + expect(globalRequest.adUnits[0].bids[0].userIdAsEids).that.is.empty; + expect(globalRequest.adUnits[0].bids[0].userId).that.is.empty; + }); + + it('filterSDAwhenEID for id5-sync.com EID ', function () { + let dataControllerConfiguration = { + 'dataController': { + filterSDAwhenEID: ['id5-sync.com'] + } + }; + config.setConfig(dataControllerConfiguration); + filterBidData(callbackFn, req); + expect(req.ortb2Fragments.bidder.ix.user.data).that.is.empty; + }); + + it('filterSDAwhenEID for All EID ', function () { + let dataControllerConfiguration = { + 'dataController': { + filterSDAwhenEID: ['*'] + } + }; + config.setConfig(dataControllerConfiguration); + + filterBidData(callbackFn, req); + expect(req.ortb2Fragments.bidder.ix.user.data).that.is.empty; + expect(req.ortb2Fragments.global.user.data).that.is.empty; + }); + + it('filterSDAwhenEID for unavailable source test-sync.com EID ', function () { + let dataControllerConfiguration = { + 'dataController': { + filterSDAwhenEID: ['test-sync.com'] + } + }; + config.setConfig(dataControllerConfiguration); + filterBidData(callbackFn, req); + expect(req.ortb2Fragments.bidder.ix.user.data).that.is.not.empty; + }); + } + ); +}); diff --git a/test/spec/modules/datawrkzBidAdapter_spec.js b/test/spec/modules/datawrkzBidAdapter_spec.js new file mode 100644 index 00000000000..3ddc2bad1cf --- /dev/null +++ b/test/spec/modules/datawrkzBidAdapter_spec.js @@ -0,0 +1,656 @@ +import { expect } from 'chai'; +import { config } from 'src/config.js'; +import { spec } from 'modules/datawrkzBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'datawrkz'; +const ENDPOINT_URL = 'https://at.datawrkz.com/exchange/openrtb23/'; +const SITE_ID = 'site_id'; +const FINAL_URL = ENDPOINT_URL + SITE_ID + '?hb=1'; + +describe('datawrkzAdapterTests', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': BIDDER_CODE, + 'params': { + 'site_id': SITE_ID, + 'bidfloor': '1.0' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when params not found', function () { + let bid = Object.assign({}, bid); + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when required site_id param not found', function () { + let bid = Object.assign({}, bid); + bid.params = {'bidfloor': '1.0'} + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when adunit is adpod video', function () { + let bid = Object.assign({}, bid); + bid.params = {'bidfloor': '1.0', 'site_id': SITE_ID}; + bid.mediaTypes = { + 'video': { + 'context': 'adpod' + } + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const consentString = '1YA-'; + const bidderRequest = { + 'bidderCode': 'datawrkz', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': consentString, + 'gdprConsent': {'gdprApplies': true}, + }; + const bannerBidRequests = [{ + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00}, + 'mediaTypes': {'banner': {'sizes': [[300, 250], [300, 600]]}}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + }]; + const bannerBidRequestsSingleArraySlotAndDeals = [{ + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00, 'deals': [{id: 'deal_1'}, {id: 'deal_2'}]}, + 'mediaTypes': {'banner': {}}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'sizes': [300, 250], + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + }]; + const nativeBidRequests = [{ + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00}, + 'mediaTypes': {'native': { + 'title': {'required': true, 'len': 80}, + 'image': {'required': true, 'sizes': [[300, 250]]}, + 'icon': {'required': true, 'sizes': [[50, 50]]}, + 'sponsoredBy': {'required': true}, + 'cta': {'required': true}, + 'body': {'required': true, 'len': 100} + }}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + }]; + const nativeBidRequestsSingleArraySlotAndDeals = [{ + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00, 'deals': [{id: 'deal_1'}, {id: 'deal_2'}]}, + 'mediaTypes': {'native': { + 'title': {'len': 80}, + 'image': {'sizes': [300, 250]}, + 'icon': {'sizes': [50, 50]}, + 'sponsoredBy': {}, + 'cta': {}, + 'body': {'len': 100} + }}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + }]; + const instreamVideoBidRequests = [{ + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00}, + 'mediaTypes': {'video': {'context': 'instream', 'playerSize': [[640, 480]]}}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + }]; + const instreamVideoBidRequestsSingleArraySlotAndDeals = [{ + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00, 'deals': [{id: 'deal_1'}, {id: 'deal_2'}]}, + 'mediaTypes': {'video': {'context': 'instream', 'playerSize': [640, 480]}}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + }]; + const outstreamVideoBidRequests = [{ + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00}, + 'mediaTypes': {'video': {'context': 'outstream', 'playerSize': [[640, 480]], 'mimes': ['video/mp4', 'video/x-flv']}}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + }]; + const outstreamVideoBidRequestsSingleArraySlotAndDeals = [{ + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00, 'deals': [{id: 'deal_1'}, {id: 'deal_2'}]}, + 'mediaTypes': {'video': {'context': 'outstream', 'playerSize': [640, 480], 'mimes': ['video/mp4', 'video/x-flv']}}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + }]; + const bidRequestsWithNoMediaType = [{ + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + }]; + + it('empty bid requests', function () { + const requests = spec.buildRequests([], bidderRequest); + assert.lengthOf(requests, 0); + }); + + it('mediaTypes missing in bid request', function () { + const requests = spec.buildRequests(bidRequestsWithNoMediaType, bidderRequest); + assert.lengthOf(requests, 0); + }); + + it('invalid media type in bid request', function () { + bidRequestsWithNoMediaType[0].mediaTypes = {'test': {}}; + const requests = spec.buildRequests(bidRequestsWithNoMediaType, bidderRequest); + assert.lengthOf(requests, 0); + }); + + it('size missing in bid request for banner', function () { + delete bidRequestsWithNoMediaType[0].mediaTypes.test; + bidRequestsWithNoMediaType[0].mediaTypes.banner = {}; + const requests = spec.buildRequests(bidRequestsWithNoMediaType, bidderRequest); + assert.lengthOf(requests, 0); + }); + + it('size array empty in bid request for banner', function () { + bidRequestsWithNoMediaType[0].mediaTypes.banner.sizes = []; + const requests = spec.buildRequests(bidRequestsWithNoMediaType, bidderRequest); + assert.lengthOf(requests, 0); + }); + + it('banner bidRequest with slot size as 2 dimensional array', function () { + sinon.stub(config, 'getConfig').withArgs('coppa').returns(true); + const requests = spec.buildRequests(bannerBidRequests, bidderRequest); + config.getConfig.restore(); + const payload = JSON.parse(requests[0].data); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(FINAL_URL); + expect(payload.imp).to.exist; + expect(payload).to.nested.include({'imp[0].banner.w': 300}); + expect(payload).to.nested.include({'imp[0].banner.h': 250}); + expect(payload).to.nested.include({'regs.ext.us_privacy': consentString}); + expect(payload).to.nested.include({'regs.ext.gdpr': '1'}); + expect(payload).to.nested.include({'regs.coppa': '1'}); + expect(requests[0].bidRequest).to.exist; + expect(requests[0].bidRequest.requestedMediaType).to.equal('banner'); + }); + + it('banner bidRequest with deals and slot size as 1 dimensional array', function () { + const requests = spec.buildRequests(bannerBidRequestsSingleArraySlotAndDeals, bidderRequest); + const payload = JSON.parse(requests[0].data); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(FINAL_URL); + expect(payload.imp).to.exist; + expect(payload).to.nested.include({'imp[0].banner.w': 300}); + expect(payload).to.nested.include({'imp[0].banner.h': 250}); + expect(payload).to.nested.include({'imp[0].pmp.deals[0].id': 'deal_1'}); + expect(payload).to.nested.include({'imp[0].pmp.deals[1].id': 'deal_2'}); + expect(requests[0].bidRequest).to.exist; + expect(requests[0].bidRequest.requestedMediaType).to.equal('banner'); + }); + + it('native bidRequest fields with slot size as 2 dimensional array', function () { + sinon.stub(config, 'getConfig').withArgs('coppa').returns(true); + const requests = spec.buildRequests(nativeBidRequests, bidderRequest); + config.getConfig.restore(); + const payload = JSON.parse(requests[0].data); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(FINAL_URL); + expect(payload.imp[0].native.request).to.exist; + expect(payload).to.nested.include({'regs.ext.us_privacy': consentString}); + expect(payload).to.nested.include({'regs.ext.gdpr': '1'}); + expect(payload).to.nested.include({'regs.coppa': '1'}); + expect(requests[0].bidRequest).to.exist; + expect(requests[0].bidRequest.requestedMediaType).to.equal('native'); + }); + + it('native bidRequest fields with deals and slot size as 1 dimensional array', function () { + const requests = spec.buildRequests(nativeBidRequestsSingleArraySlotAndDeals, bidderRequest); + const payload = JSON.parse(requests[0].data); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(FINAL_URL); + expect(payload.imp).to.exist; + expect(payload.imp[0].native.request).to.exist; + expect(payload).to.nested.include({'imp[0].pmp.deals[0].id': 'deal_1'}); + expect(payload).to.nested.include({'imp[0].pmp.deals[1].id': 'deal_2'}); + expect(requests[0].bidRequest).to.exist; + expect(requests[0].bidRequest.requestedMediaType).to.equal('native'); + }); + + it('instream video bidRequest fields with slot size as 2 dimensional array', function () { + sinon.stub(config, 'getConfig').withArgs('coppa').returns(true); + const requests = spec.buildRequests(instreamVideoBidRequests, bidderRequest); + config.getConfig.restore(); + const payload = JSON.parse(requests[0].data); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(FINAL_URL); + expect(payload).to.nested.include({'regs.ext.us_privacy': consentString}); + expect(payload).to.nested.include({'regs.ext.gdpr': '1'}); + expect(payload).to.nested.include({'regs.coppa': '1'}); + expect(requests[0].bidRequest).to.exist; + expect(requests[0].bidRequest.requestedMediaType).to.equal('video'); + }); + + it('instream video bidRequest with deals and slot size as 1 dimensional array', function () { + const requests = spec.buildRequests(instreamVideoBidRequestsSingleArraySlotAndDeals, bidderRequest); + const payload = JSON.parse(requests[0].data); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(FINAL_URL); + expect(requests[0].bidRequest).to.exist; + expect(requests[0].bidRequest.requestedMediaType).to.equal('video'); + }); + + it('outstream video bidRequest fields with slot size as 2 dimensional array', function () { + sinon.stub(config, 'getConfig').withArgs('coppa').returns(true); + const requests = spec.buildRequests(outstreamVideoBidRequests, bidderRequest); + config.getConfig.restore(); + const payload = JSON.parse(requests[0].data); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(FINAL_URL); + expect(payload).to.nested.include({'imp[0].video.w': 640}); + expect(payload).to.nested.include({'imp[0].video.h': 480}); + expect(payload).to.nested.include({'regs.ext.us_privacy': consentString}); + expect(payload).to.nested.include({'regs.ext.gdpr': '1'}); + expect(payload).to.nested.include({'regs.coppa': '1'}); + expect(requests[0].bidRequest).to.exist; + expect(requests[0].bidRequest.requestedMediaType).to.equal('video'); + }); + + it('outstream video bidRequest fields with deals and slot size as 1 dimensional array', function () { + const requests = spec.buildRequests(outstreamVideoBidRequestsSingleArraySlotAndDeals, bidderRequest); + const payload = JSON.parse(requests[0].data); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(FINAL_URL); + expect(payload).to.nested.include({'imp[0].video.w': 640}); + expect(payload).to.nested.include({'imp[0].video.h': 480}); + expect(payload).to.nested.include({'imp[0].pmp.deals[0].id': 'deal_1'}); + expect(payload).to.nested.include({'imp[0].pmp.deals[1].id': 'deal_2'}); + expect(requests[0].bidRequest).to.exist; + expect(requests[0].bidRequest.requestedMediaType).to.equal('video'); + }); + }); + + describe('interpretResponse', function () { + const bidRequest = { + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00}, + 'mediaTypes': {'banner': {'sizes': [[300, 250], [300, 600]]}}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + 'requestedMediaType': 'banner' + }; + const nativeBidRequest = { + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00}, + 'mediaTypes': {'native': { + 'title': {'required': true, 'len': 80}, + 'image': {'required': true, 'sizes': [300, 250]}, + 'icon': {'required': true, 'sizes': [50, 50]}, + 'sponsoredBy': {'required': true}, + 'cta': {'required': true}, + 'body': {'required': true} + }}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + 'requestedMediaType': 'native', + 'assets': [ + {'id': 1, 'required': 1, 'title': {'len': 80}}, + {'id': 2, 'required': 1, 'img': {'type': 3, 'w': 300, 'h': 250}}, + {'id': 3, 'required': 1, 'img': {'type': 1, 'w': 50, 'h': 50}}, + {'id': 4, 'required': 1, 'data': {'type': 1}}, + {'id': 5, 'required': 1, 'data': {'type': 12}}, + {'id': 6, 'required': 1, 'data': {'type': 2, 'len': 100}} + ] + }; + const instreamVideoBidRequest = { + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, 'bidfloor': 1.00}, + 'mediaTypes': {'video': {'context': 'instream', 'playerSize': [640, 480]}}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + 'requestedMediaType': 'video' + }; + const outstreamVideoBidRequest = { + 'bidder': BIDDER_CODE, + 'params': {'site_id': SITE_ID, + 'bidfloor': 1.00, + 'outstreamType': 'slider_top_left', + 'outstreamConfig': + {'ad_unit_audio': 1, 'show_player_close_button_after': 5, 'hide_player_control': 0}}, + 'mediaTypes': {'video': {'context': 'outstream', 'playerSize': [640, 480]}}, + 'adUnitCode': 'adUnitCode', + 'transactionId': 'transactionId', + 'bidId': 'bidId', + 'bidderRequestId': 'bidderRequestId', + 'auctionId': 'auctionId', + 'requestedMediaType': 'video' + }; + const request = { + 'method': 'POST', + 'url': FINAL_URL, + 'data': '', + bidRequest + }; + + it('check empty response', function () { + const result = spec.interpretResponse({}, request); + expect(result).to.deep.equal([]); + }); + + it('check if id missing in response', function () { + const serverResponse = {'body': {'seatbid': [{}]}, 'headers': {}}; + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.deep.equal([]); + }); + + it('check if seatbid present in response', function () { + const serverResponse = {'body': {'id': 'id'}, 'headers': {}}; + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.deep.equal([]); + }); + + it('check empty array response seatbid', function () { + const serverResponse = {'body': {'id': 'id', 'seatbid': []}, 'headers': {}}; + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.deep.equal([]); + }); + + it('check bid present in seatbid', function () { + const serverResponse = {'body': {'id': 'id', 'seatbid': [{}]}, 'headers': {}}; + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.have.lengthOf(0); + }); + + it('check empty array bid in seatbid', function () { + const serverResponse = {'body': {'id': 'id', 'seatbid': [{'bid': []}]}, 'headers': {}}; + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.have.lengthOf(0); + }); + + it('banner response missing bid price', function () { + const serverResponse = {'body': {'id': 'id', 'seatbid': [{'bid': [{'id': 1}]}]}, 'headers': {}}; + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.have.lengthOf(1); + expect(result[0].requestId).to.equal('bidId'); + expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); + }); + + it('banner response', function () { + const serverResponse = { + 'body': { + 'id': 'id', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1', + 'price': 1, + 'w': 300, + 'h': 250, + 'adm': 'test adm', + 'nurl': 'url' + } + ] + } + ] + }, + 'headers': {} + }; + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.have.lengthOf(1); + expect(result[0].requestId).to.equal('bidId'); + expect(result[0].cpm).to.equal(1); + expect(result[0].size).to.equal(bidRequest.mediaTypes.banner.sizes); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].ad).to.equal(decodeURIComponent(serverResponse.body.seatbid[0].bid[0].adm + '')); + expect(result[0].creativeId).to.equal('1'); + expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); + expect(result[0].transactionId).to.equal(request.bidRequest.transactionId); + expect(result[0].mediaType).to.equal('banner'); + }); + + it('native response missing bid price', function () { + request.bidRequest = nativeBidRequest; + const serverResponse = { + 'body': { + 'id': 'id', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1', + 'w': 300, + 'h': 250, + 'adm': '{"native": {"link": {"url": "test_url"}, "imptrackers": [], "assets": [' + + '{"id": 1, "title": {"text": "Test title"}},' + + '{"id": 2, "img": {"type": 3,"url": "https://test/image", "w": 300, "h": 250}},' + + '{"id": 3, "img": {"type": 1, "url": "https://test/icon", "w": 50, "h": 50}},' + + '{"id": 4, "data": {"type": 1, "value": "Test sponsored by"}},' + + '{"id": 5, "data": {"type": 12, "value": "Test CTA"}},' + + '{"id": 6, "data": {"type": 2, "value": "Test body"}}]}}' + } + ] + } + ] + }, + 'headers': {} + }; + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.have.lengthOf(1); + expect(result[0].requestId).to.equal('bidId'); + expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); + }); + + it('native response', function () { + request.bidRequest = nativeBidRequest; + const serverResponse = { + 'body': { + 'id': 'id', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1', + 'price': 1, + 'w': 300, + 'h': 250, + 'adm': '{"native": {"link": {"url": "test_url"}, "imptrackers": ["tracker1", "tracker2"], "assets": [' + + '{"id": 1, "title": {"text": "Test title"}},' + + '{"id": 2, "img": {"type": 3,"url": "https://test/image", "w": 300, "h": 250}},' + + '{"id": 3, "img": {"type": 1, "url": "https://test/icon", "w": 50, "h": 50}},' + + '{"id": 4, "data": {"type": 1, "value": "Test sponsored by"}},' + + '{"id": 5, "data": {"type": 12, "value": "Test CTA"}},' + + '{"id": 6, "data": {"type": 2, "value": "Test body"}}]}}' + } + ] + } + ] + }, + 'headers': {} + }; + + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.have.lengthOf(1); + expect(result[0].requestId).to.equal('bidId'); + expect(result[0].cpm).to.equal(1); + expect(result[0].native.clickUrl).to.equal('test_url'); + expect(result[0].native.impressionTrackers).to.have.lengthOf(2); + expect(result[0].native.title).to.equal('Test title'); + expect(result[0].native.image.url).to.equal('https://test/image'); + expect(result[0].native.icon.url).to.equal('https://test/icon'); + expect(result[0].native.sponsored).to.equal('Test sponsored by'); + expect(result[0].native.cta).to.equal('Test CTA'); + expect(result[0].native.desc).to.equal('Test body'); + expect(result[0].creativeId).to.equal('1'); + expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); + expect(result[0].transactionId).to.equal(request.bidRequest.transactionId); + expect(result[0].mediaType).to.equal('native'); + }); + + it('video response missing bid price', function () { + request.bidRequest = instreamVideoBidRequest; + const serverResponse = { + 'body': { + 'id': 'id', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1', + 'w': 640, + 'h': 480, + 'adm': '', + 'ext': { + 'vast_url': 'vast_url' + } + } + ] + } + ] + }, + 'headers': {} + }; + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.have.lengthOf(1); + expect(result[0].requestId).to.equal('bidId'); + expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); + }); + + it('instream video response', function () { + request.bidRequest = instreamVideoBidRequest; + const serverResponse = { + 'body': { + 'id': 'id', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1', + 'price': 1, + 'w': 640, + 'h': 480, + 'adm': '', + 'ext': { + 'vast_url': 'test_vast_url?kcid=123&kaid=12&protocol=3' + } + } + ] + } + ] + }, + 'headers': {} + }; + + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.have.lengthOf(1); + expect(result[0].requestId).to.equal('bidId'); + expect(result[0].cpm).to.equal(1); + expect(result[0].width).to.equal(640); + expect(result[0].height).to.equal(480); + expect(result[0].vastUrl).to.equal('test_vast_url?kcid=123&kaid=12&protocol=3'); + expect(result[0].adserverTargeting.hb_kcid).to.equal('123'); + expect(result[0].adserverTargeting.hb_kaid).to.equal('12'); + expect(result[0].adserverTargeting.hb_protocol).to.equal('3'); + expect(result[0].creativeId).to.equal('1'); + expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); + expect(result[0].transactionId).to.equal(request.bidRequest.transactionId); + expect(result[0].mediaType).to.equal('video'); + }); + + it('outstream video response', function () { + request.bidRequest = outstreamVideoBidRequest; + const serverResponse = { + 'body': { + 'id': 'id', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1', + 'price': 1, + 'w': 640, + 'h': 480, + 'adm': '', + 'ext': { + 'vast_url': 'vast_url' + } + } + ] + } + ] + }, + 'headers': {} + }; + const result = spec.interpretResponse(serverResponse, request); + expect(result).to.have.lengthOf(1); + expect(result[0].requestId).to.equal('bidId'); + expect(result[0].cpm).to.equal(1); + expect(result[0].width).to.equal(640); + expect(result[0].height).to.equal(480); + expect(result[0].outstreamType).to.equal('slider_top_left'); + expect(result[0].ad).to.equal(''); + expect(result[0].renderer).to.exist; + expect(result[0].creativeId).to.equal('1'); + expect(result[0].bidderCode).to.equal(request.bidRequest.bidder); + expect(result[0].transactionId).to.equal(request.bidRequest.transactionId); + expect(result[0].mediaType).to.equal('video'); + }); + }); +}); diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js index 79866d023e9..360c670cf3b 100644 --- a/test/spec/modules/debugging_mod_spec.js +++ b/test/spec/modules/debugging_mod_spec.js @@ -1,13 +1,31 @@ import {expect} from 'chai'; import {BidInterceptor} from '../../../modules/debugging/bidInterceptor.js'; -import {bidderBidInterceptor} from '../../../modules/debugging/index.js'; -import {pbsBidInterceptor} from '../../../modules/debugging/pbsInterceptor.js'; +import { + bidderBidInterceptor, + disableDebugging, + getConfig, + sessionLoader, +} from '../../../modules/debugging/debugging.js'; +import '../../../modules/debugging/index.js'; +import {makePbsInterceptor} from '../../../modules/debugging/pbsInterceptor.js'; +import {config} from '../../../src/config.js'; +import {hook} from '../../../src/hook.js'; +import { + addBidderRequestsBound, + addBidderRequestsHook, + addBidResponseBound, + addBidResponseHook, +} from '../../../modules/debugging/legacy.js'; + +import {addBidderRequests, addBidResponse} from '../../../src/auction.js'; +import {prefixLog} from '../../../src/utils.js'; +import {createBid} from '../../../src/bidfactory.js'; describe('bid interceptor', () => { let interceptor, mockSetTimeout; beforeEach(() => { mockSetTimeout = sinon.stub().callsFake((fn) => fn()); - interceptor = new BidInterceptor({setTimeout: mockSetTimeout}); + interceptor = new BidInterceptor({setTimeout: mockSetTimeout, logger: prefixLog('TEST')}); }); function setRules(...rules) { @@ -116,6 +134,13 @@ describe('bid interceptor', () => { expect(result).to.include.keys(REQUIRED_KEYS); expect(result.outer.inner).to.eql({key: 'value'}); }); + + it('should respect array vs object definitions', () => { + const result = matchingRule({replace: {item: [replDef]}}).replace({}); + expect(result.item).to.be.an('array'); + expect(result.item.length).to.equal(1); + expect(result.item[0]).to.eql({key: 'value'}); + }); }); }); @@ -350,6 +375,7 @@ describe('pbsBidInterceptor', () => { interceptResults = [EMPTY_INT_RES, EMPTY_INT_RES]; }); + const pbsBidInterceptor = makePbsInterceptor({createBid}); function callInterceptor() { return pbsBidInterceptor(next, interceptBids, s2sBidRequest, bidRequests, ajax, {onResponse, onError, onBid}); } @@ -449,3 +475,212 @@ describe('pbsBidInterceptor', () => { }); }); }); + +describe('bid overrides', function () { + let sandbox; + const logger = prefixLog('TEST'); + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + window.sessionStorage.clear(); + config.resetConfig(); + sandbox.restore(); + }); + + describe('initialization', function () { + beforeEach(function () { + sandbox.stub(config, 'setConfig'); + }); + + afterEach(function () { + disableDebugging({hook, logger}); + }); + + it('should happen when enabled with setConfig', function () { + getConfig({ + enabled: true + }, {config, hook, logger}); + + expect(addBidResponse.getHooks().some(hook => hook.hook === addBidResponseBound)).to.equal(true); + expect(addBidderRequests.getHooks().some(hook => hook.hook === addBidderRequestsBound)).to.equal(true); + }); + it('should happen when configuration found in sessionStorage', function () { + sessionLoader({ + storage: {getItem: () => ('{"enabled": true}')}, + config, + hook, + logger + }); + expect(addBidResponse.getHooks().some(hook => hook.hook === addBidResponseBound)).to.equal(true); + expect(addBidderRequests.getHooks().some(hook => hook.hook === addBidderRequestsBound)).to.equal(true); + }); + + it('should not throw if sessionStorage is inaccessible', function () { + expect(() => { + sessionLoader({ + getItem() { + throw new Error('test'); + } + }); + }).not.to.throw(); + }); + }); + + describe('bidResponse hook', function () { + let mockBids; + let bids; + + beforeEach(function () { + let baseBid = { + 'bidderCode': 'rubicon', + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'mediaType': 'banner', + 'source': 'client', + 'currency': 'USD', + 'cpm': 0.5, + 'ttl': 300, + 'netRevenue': false, + 'adUnitCode': '/19968336/header-bid-tag-0' + }; + mockBids = []; + mockBids.push(baseBid); + mockBids.push(Object.assign({}, baseBid, { + bidderCode: 'appnexus' + })); + + bids = []; + }); + + function run(overrides) { + mockBids.forEach(bid => { + let next = (adUnitCode, bid) => { + bids.push(bid); + }; + addBidResponseHook.bind({overrides, logger})(next, bid.adUnitCode, bid); + }); + } + + it('should allow us to exclude bidders', function () { + run({ + enabled: true, + bidders: ['appnexus'] + }); + + expect(bids.length).to.equal(1); + expect(bids[0].bidderCode).to.equal('appnexus'); + }); + + it('should allow us to override all bids', function () { + run({ + enabled: true, + bids: [{ + cpm: 2 + }] + }); + + expect(bids.length).to.equal(2); + sinon.assert.match(bids[0], { + cpm: 2, + isDebug: true, + }); + sinon.assert.match(bids[1], { + cpm: 2, + isDebug: true, + }); + }); + + it('should allow us to override bids by bidder', function () { + run({ + enabled: true, + bids: [{ + bidder: 'rubicon', + cpm: 2 + }] + }); + + expect(bids.length).to.equal(2); + sinon.assert.match(bids[0], { + cpm: 2, + isDebug: true + }); + sinon.assert.match(bids[1], { + cpm: 0.5, + isDebug: sinon.match.falsy + }); + }); + + it('should allow us to override bids by adUnitCode', function () { + mockBids[1].adUnitCode = 'test'; + + run({ + enabled: true, + bids: [{ + adUnitCode: 'test', + cpm: 2 + }] + }); + + expect(bids.length).to.equal(2); + sinon.assert.match(bids[0], { + cpm: 0.5, + isDebug: sinon.match.falsy, + }); + sinon.assert.match(bids[1], { + cpm: 2, + isDebug: true, + }); + }); + }); + + describe('bidRequests hook', function () { + let mockBidRequests; + let bidderRequests; + + beforeEach(function () { + let baseBidderRequest = { + 'bidderCode': 'rubicon', + 'bids': [{ + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'mediaType': 'banner', + 'source': 'client', + 'currency': 'USD', + 'cpm': 0.5, + 'ttl': 300, + 'netRevenue': false, + 'adUnitCode': '/19968336/header-bid-tag-0' + }] + }; + mockBidRequests = []; + mockBidRequests.push(baseBidderRequest); + mockBidRequests.push(Object.assign({}, baseBidderRequest, { + bidderCode: 'appnexus' + })); + + bidderRequests = []; + }); + + function run(overrides) { + let next = (b) => { + bidderRequests = b; + }; + addBidderRequestsHook.bind({overrides, logger})(next, mockBidRequests); + } + + it('should allow us to exclude bidders', function () { + run({ + enabled: true, + bidders: ['appnexus'] + }); + + expect(bidderRequests.length).to.equal(1); + expect(bidderRequests[0].bidderCode).to.equal('appnexus'); + }); + }); +}); diff --git a/test/spec/modules/deltaprojectsBidAdapter_spec.js b/test/spec/modules/deltaprojectsBidAdapter_spec.js index 382415eab62..b966d1580ca 100644 --- a/test/spec/modules/deltaprojectsBidAdapter_spec.js +++ b/test/spec/modules/deltaprojectsBidAdapter_spec.js @@ -7,6 +7,7 @@ import { } from 'modules/deltaprojectsBidAdapter.js'; const BID_REQ_REFER = 'http://example.com/page?param=val'; +const BID_REQ_DOMAIN = 'example.com' describe('deltaprojectsBidAdapter', function() { describe('isBidRequestValid', function () { @@ -62,7 +63,7 @@ describe('deltaprojectsBidAdapter', function() { auctionId: '1d1a030790a475', } const bidRequests = [BIDREQ]; - const bannerRequest = spec.buildRequests(bidRequests, {refererInfo: { referer: BID_REQ_REFER }})[0]; + const bannerRequest = spec.buildRequests(bidRequests, {refererInfo: { page: BID_REQ_REFER, domain: BID_REQ_DOMAIN }})[0]; const bannerRequestBody = bannerRequest.data; it('send bid request with test tag if it is set in the param', function () { diff --git a/test/spec/modules/dgkeywordRtdProvider_spec.js b/test/spec/modules/dgkeywordRtdProvider_spec.js index a145f429557..5ecec48f6b4 100644 --- a/test/spec/modules/dgkeywordRtdProvider_spec.js +++ b/test/spec/modules/dgkeywordRtdProvider_spec.js @@ -293,8 +293,8 @@ describe('Digital Garage Keyword Module', function () { moduleConfig, null ); + const request = server.requests[0]; setTimeout(() => { - const request = server.requests[0]; if (request) { request.respond( 200, diff --git a/test/spec/modules/dianomiBidAdapter_spec.js b/test/spec/modules/dianomiBidAdapter_spec.js new file mode 100644 index 00000000000..f1c3015adb8 --- /dev/null +++ b/test/spec/modules/dianomiBidAdapter_spec.js @@ -0,0 +1,1223 @@ +// jshint esversion: 6, es3: false, node: true +import { assert } from 'chai'; +import { spec } from 'modules/dianomiBidAdapter.js'; +import { config } from 'src/config.js'; +import { createEidsArray } from 'modules/userId/eids.js'; + +describe('Dianomi adapter', () => { + let bids = []; + + describe('isBidRequestValid', () => { + let bid = { + bidder: 'dianomi', + params: { + smartadId: 1234, + }, + }; + + it('should return true when required params found', () => { + assert(spec.isBidRequestValid(bid)); + bid.params = { + smartadId: 4332, + }; + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when required params are missing', () => { + bid.params = {}; + assert.isFalse(spec.isBidRequestValid(bid)); + + bid.params = { + smartadId: null, + }; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests', () => { + beforeEach(() => { + config.resetConfig(); + }); + it('should send request with correct structure', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + }, + ]; + let request = spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }); + + assert.equal(request.method, 'POST'); + assert.equal(request.url, 'https://www-prebid.dianomi.com/cgi-bin/smartads_prebid.pl'); + assert.ok(request.data); + }); + + describe('user privacy', () => { + it('should send GDPR Consent data to Dianomi if gdprApplies', () => { + let validBidRequests = [{ bidId: 'bidId', params: { smartadId: 1234 } }]; + let bidderRequest = { + gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, + refererInfo: { page: 'page' }, + }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.user.ext.consent, bidderRequest.gdprConsent.consentString); + assert.equal(request.regs.ext.gdpr, bidderRequest.gdprConsent.gdprApplies); + assert.equal(typeof request.regs.ext.gdpr, 'number'); + }); + + it('should send gdpr as number', () => { + let validBidRequests = [{ bidId: 'bidId', params: { smartadId: 1234 } }]; + let bidderRequest = { + gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, + refererInfo: { page: 'page' }, + }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(typeof request.regs.ext.gdpr, 'number'); + assert.equal(request.regs.ext.gdpr, 1); + }); + + it('should send CCPA Consent data to dianomi', () => { + let validBidRequests = [{ bidId: 'bidId', params: { smartadId: 1234 } }]; + let bidderRequest = { uspConsent: '1YA-', refererInfo: { page: 'page' } }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.regs.ext.us_privacy, '1YA-'); + + bidderRequest = { + uspConsent: '1YA-', + gdprConsent: { gdprApplies: true, consentString: 'consentDataString' }, + refererInfo: { page: 'page' }, + }; + request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.regs.ext.us_privacy, '1YA-'); + assert.equal(request.user.ext.consent, 'consentDataString'); + assert.equal(request.regs.ext.gdpr, 1); + }); + + it('should not send GDPR Consent data to dianomi if gdprApplies is undefined', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + }, + ]; + let bidderRequest = { + gdprConsent: { gdprApplies: false, consentString: 'consentDataString' }, + refererInfo: { page: 'page' }, + }; + let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.user.ext.consent, 'consentDataString'); + assert.equal(request.regs.ext.gdpr, 0); + + bidderRequest = { + gdprConsent: { consentString: 'consentDataString' }, + refererInfo: { page: 'page' }, + }; + request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.user, undefined); + assert.equal(request.regs, undefined); + }); + it('should send default GDPR Consent data to dianomi', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + }, + ]; + let request = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ); + + assert.equal(request.user, undefined); + assert.equal(request.regs, undefined); + }); + }); + + it('should have default request structure', () => { + let keys = 'site,device,source,ext,imp'.split(','); + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + }, + ]; + let request = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ); + let data = Object.keys(request); + + assert.deepEqual(keys, data); + }); + + it('should set request keys correct values', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + transactionId: 'transactionId', + }, + ]; + let request = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ); + + assert.equal(request.source.tid, validBidRequests[0].transactionId); + assert.equal(request.source.fd, 1); + }); + + it('should send info about device', () => { + config.setConfig({ + device: { w: 100, h: 100 }, + }); + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + }, + ]; + let request = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ); + + assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.device.w, 100); + assert.equal(request.device.h, 100); + }); + + it('should send app info', () => { + config.setConfig({ + app: { id: 'appid' }, + }); + const ortb2 = { app: { name: 'appname' } }; + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + ortb2 + }, + ]; + let request = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' }, ortb2 }).data + ); + + assert.equal(request.app.id, 'appid'); + assert.equal(request.app.name, 'appname'); + assert.equal(request.site, undefined); + }); + + it('should send info about the site', () => { + config.setConfig({ + site: { + id: '123123', + publisher: { + domain: 'publisher.domain.com', + }, + }, + }); + const ortb2 = { + site: { + publisher: { + name: "publisher's name", + }, + }, + } + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + ortb2 + }, + ]; + let refererInfo = { page: 'page' }; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo, ortb2 }).data); + + assert.deepEqual(request.site, { + page: refererInfo.page, + publisher: { + domain: 'publisher.domain.com', + name: "publisher's name", + }, + id: '123123', + }); + }); + + it('should pass extended ids', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + userIdAsEids: createEidsArray({ + tdid: 'TTD_ID_FROM_USER_ID_MODULE', + pubcid: 'pubCommonId_FROM_USER_ID_MODULE', + }), + }, + ]; + + let request = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ); + assert.deepEqual(request.user.ext.eids, [ + { + source: 'adserver.org', + uids: [{ id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } }], + }, + { source: 'pubcid.org', uids: [{ id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 }] }, + ]); + }); + + it('should send currency if defined', () => { + config.setConfig({ currency: { adServerCurrency: 'EUR' } }); + let validBidRequests = [{ params: { smartadId: 1234 } }]; + let refererInfo = { page: 'page' }; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo }).data); + + assert.deepEqual(request.cur, ['EUR']); + }); + + it('should pass supply chain object', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + schain: { + validation: 'strict', + config: { + ver: '1.0', + }, + }, + }, + ]; + + let request = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ); + assert.deepEqual(request.source.ext.schain, { + validation: 'strict', + config: { + ver: '1.0', + }, + }); + }); + + describe('priceType', () => { + it('should send default priceType', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + }, + ]; + let request = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ); + + assert.equal(request.ext.pt, 'net'); + }); + it('should send correct priceType value', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + }, + ]; + let request = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ); + + assert.equal(request.ext.pt, 'net'); + }); + }); + + describe('bids', () => { + it('should add more than one bid to the request', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + }, + { + bidId: 'bidId2', + params: { smartadId: 1234 } + }, + ]; + let request = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ); + + assert.equal(request.imp.length, 2); + }); + it('should add incrementing values of id', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + mediaTypes: { video: {} }, + }, + { + bidId: 'bidId2', + params: { smartadId: 1234 }, + mediaTypes: { video: {} }, + }, + { + bidId: 'bidId3', + params: { smartadId: 1234 }, + mediaTypes: { video: {} }, + }, + ]; + let imps = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ).imp; + + for (let i = 0; i < 3; i++) { + assert.equal(imps[i].id, i + 1); + } + }); + + describe('price floors', () => { + it('should not add if floors module not configured', () => { + const validBidRequests = [ + { bidId: 'bidId', params: { smartadId: 1234 }, mediaTypes: { video: {} } }, + ]; + let imp = getRequestImps(validBidRequests)[0]; + + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, undefined); + }); + + it('should not add if floor price not defined', () => { + const validBidRequests = [getBidWithFloor()]; + let imp = getRequestImps(validBidRequests)[0]; + + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, 'USD'); + }); + + it('should request floor price in adserver currency', () => { + config.setConfig({ currency: { adServerCurrency: 'GBP' } }); + const validBidRequests = [getBidWithFloor()]; + let imp = getRequestImps(validBidRequests)[0]; + + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, 'GBP'); + }); + + it('should add correct floor values', () => { + const expectedFloors = [1, 1.3, 0.5]; + const validBidRequests = expectedFloors.map(getBidWithFloor); + let imps = getRequestImps(validBidRequests); + + expectedFloors.forEach((floor, index) => { + assert.equal(imps[index].bidfloor, floor); + assert.equal(imps[index].bidfloorcur, 'USD'); + }); + }); + + function getBidWithFloor(floor) { + return { + params: { smartadId: 1234 }, + mediaTypes: { video: {} }, + getFloor: ({ currency }) => { + return { + currency: currency, + floor, + }; + }, + }; + } + }); + + describe('multiple media types', () => { + it('should use all configured media types for bidding', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + mediaTypes: { + banner: { + sizes: [ + [100, 100], + [200, 300], + ], + }, + video: {}, + }, + }, + { + bidId: 'bidId1', + params: { smartadId: 1234 }, + mediaTypes: { + video: {}, + native: {}, + }, + }, + { + bidId: 'bidId2', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + }, + mediaTypes: { + banner: { + sizes: [ + [100, 100], + [200, 300], + ], + }, + native: {}, + video: {}, + }, + }, + ]; + let [first, second, third] = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ).imp; + + assert.ok(first.banner); + assert.ok(first.video); + assert.equal(first.native, undefined); + + assert.ok(second.video); + assert.equal(second.banner, undefined); + assert.equal(second.native, undefined); + + assert.ok(third.native); + assert.ok(third.video); + assert.ok(third.banner); + }); + }); + + describe('banner', () => { + it('should convert sizes to openrtb format', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + mediaTypes: { + banner: { + sizes: [ + [100, 100], + [200, 300], + ], + }, + }, + }, + ]; + let { banner } = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ).imp[0]; + assert.deepEqual(banner, { + format: [ + { w: 100, h: 100 }, + { w: 200, h: 300 }, + ], + }); + }); + }); + + describe('video', () => { + it('should pass video mediatype config', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + }, + }, + }, + ]; + let { video } = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ).imp[0]; + assert.deepEqual(video, { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + }); + }); + }); + + describe('native', () => { + describe('assets', () => { + it('should set correct asset id', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { + required: false, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: ['image/jpg', 'image/gif'], + }, + body: { len: 140 }, + }, + }, + ]; + let assets = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ).imp[0].native.assets; + + assert.equal(assets[0].id, 0); + assert.equal(assets[1].id, 3); + assert.equal(assets[2].id, 4); + }); + it('should add required key if it is necessary', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { + required: false, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: ['image/jpg', 'image/gif'], + }, + body: { len: 140 }, + sponsoredBy: { required: true, len: 140 }, + }, + }, + ]; + + let assets = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ).imp[0].native.assets; + + assert.equal(assets[0].required, 1); + assert.ok(!assets[1].required); + assert.ok(!assets[2].required); + assert.equal(assets[3].required, 1); + }); + + it('should map img and data assets', () => { + let validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { required: true, sizes: [150, 50] }, + icon: { required: false, sizes: [50, 50] }, + body: { required: false, len: 140 }, + sponsoredBy: { required: true }, + cta: { required: false }, + clickUrl: { required: false }, + }, + }, + ]; + + let assets = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ).imp[0].native.assets; + assert.ok(assets[0].title); + assert.equal(assets[0].title.len, 140); + assert.deepEqual(assets[1].img, { type: 3, w: 150, h: 50 }); + assert.deepEqual(assets[2].img, { type: 1, w: 50, h: 50 }); + assert.deepEqual(assets[3].data, { type: 2, len: 140 }); + assert.deepEqual(assets[4].data, { type: 1 }); + assert.deepEqual(assets[5].data, { type: 12 }); + assert.ok(!assets[6]); + }); + + describe('icon/image sizing', () => { + it('should flatten sizes and utilise first pair', () => { + const validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + nativeParams: { + image: { + sizes: [ + [200, 300], + [100, 200], + ], + }, + }, + }, + ]; + + let assets = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ).imp[0].native.assets; + assert.ok(assets[0].img); + assert.equal(assets[0].img.w, 200); + assert.equal(assets[0].img.h, 300); + }); + }); + + it('should utilise aspect_ratios', () => { + const validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + nativeParams: { + image: { + aspect_ratios: [ + { + min_width: 100, + ratio_height: 3, + ratio_width: 1, + }, + ], + }, + icon: { + aspect_ratios: [ + { + min_width: 10, + ratio_height: 5, + ratio_width: 2, + }, + ], + }, + }, + }, + ]; + + let assets = JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ).imp[0].native.assets; + assert.ok(assets[0].img); + assert.equal(assets[0].img.wmin, 100); + assert.equal(assets[0].img.hmin, 300); + + assert.ok(assets[1].img); + assert.equal(assets[1].img.wmin, 10); + assert.equal(assets[1].img.hmin, 25); + }); + + it('should not throw error if aspect_ratios config is not defined', () => { + const validBidRequests = [ + { + bidId: 'bidId', + params: { smartadId: 1234 }, + nativeParams: { + image: { + aspect_ratios: [], + }, + icon: { + aspect_ratios: [], + }, + }, + }, + ]; + + assert.doesNotThrow(() => + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }) + ); + }); + }); + }); + }); + + function getRequestImps(validBidRequests) { + return JSON.parse( + spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data + ).imp; + } + }); + + describe('interpretResponse', () => { + it('should return if no body in response', () => { + let serverResponse = {}; + let bidRequest = {}; + + assert.ok(!spec.interpretResponse(serverResponse, bidRequest)); + }); + it('should return more than one bids', () => { + let serverResponse = { + body: { + seatbid: [ + { + bid: [ + { + impid: '1', + native: { + ver: '1.1', + link: { url: 'link' }, + assets: [{ id: 1, title: { text: 'Asset title text' } }], + }, + }, + ], + }, + { + bid: [ + { + impid: '2', + native: { + ver: '1.1', + link: { url: 'link' }, + assets: [{ id: 1, data: { value: 'Asset title text' } }], + }, + }, + ], + }, + ], + }, + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { + required: false, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: ['image/jpg', 'image/gif'], + }, + body: { len: 140 }, + }, + }, + { + bidId: 'bidId2', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { + required: false, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: ['image/jpg', 'image/gif'], + }, + body: { len: 140 }, + }, + }, + ], + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(spec.interpretResponse(serverResponse, bidRequest).length, 2); + }); + + it('should parse seatbids', () => { + let serverResponse = { + body: { + seatbid: [ + { + bid: [ + { + impid: '1', + native: { + ver: '1.1', + link: { url: 'link1' }, + assets: [{ id: 1, title: { text: 'Asset title text' } }], + }, + }, + { + impid: '4', + native: { + ver: '1.1', + link: { url: 'link4' }, + assets: [{ id: 1, title: { text: 'Asset title text' } }], + }, + }, + ], + }, + { + bid: [ + { + impid: '2', + native: { + ver: '1.1', + link: { url: 'link2' }, + assets: [{ id: 1, data: { value: 'Asset title text' } }], + }, + }, + ], + }, + ], + }, + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { + required: false, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: ['image/jpg', 'image/gif'], + }, + body: { len: 140 }, + }, + }, + { + bidId: 'bidId2', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { + required: false, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: ['image/jpg', 'image/gif'], + }, + body: { len: 140 }, + }, + }, + { + bidId: 'bidId3', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { + required: false, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: ['image/jpg', 'image/gif'], + }, + body: { len: 140 }, + }, + }, + { + bidId: 'bidId4', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { + required: false, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: ['image/jpg', 'image/gif'], + }, + body: { len: 140 }, + }, + }, + ], + }; + + bids = spec.interpretResponse(serverResponse, bidRequest).map((bid) => { + const { + requestId, + native: { clickUrl }, + } = bid; + return [requestId, clickUrl]; + }); + + assert.equal(bids.length, 3); + assert.deepEqual(bids, [ + ['bidId1', 'link1'], + ['bidId2', 'link2'], + ['bidId4', 'link4'], + ]); + }); + + it('should set correct values to bid', () => { + let serverResponse = { + body: { + id: null, + bidid: null, + seatbid: [ + { + bid: [ + { + impid: '1', + price: 93.1231, + crid: '12312312', + native: { + assets: [], + link: { url: 'link' }, + imptrackers: ['imptrackers url1', 'imptrackers url2'], + }, + dealid: 'deal-id', + adomain: ['demo.com'], + ext: { + prebid: { + type: 'native', + }, + }, + }, + ], + }, + ], + cur: 'USD', + }, + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { smartadId: 1234 }, + nativeParams: { + title: { required: true, len: 140 }, + image: { + required: false, + wmin: 836, + hmin: 627, + w: 325, + h: 300, + mimes: ['image/jpg', 'image/gif'], + }, + body: { len: 140 }, + }, + }, + ], + }; + + const bids = spec.interpretResponse(serverResponse, bidRequest); + const bid = serverResponse.body.seatbid[0].bid[0]; + assert.deepEqual(bids[0].requestId, bidRequest.bids[0].bidId); + assert.deepEqual(bids[0].cpm, bid.price); + assert.deepEqual(bids[0].creativeId, bid.crid); + assert.deepEqual(bids[0].ttl, 360); + assert.deepEqual(bids[0].netRevenue, false); + assert.deepEqual(bids[0].currency, serverResponse.body.cur); + assert.deepEqual(bids[0].mediaType, 'native'); + assert.deepEqual(bids[0].meta.mediaType, 'native'); + assert.deepEqual(bids[0].meta.advertiserDomains, ['demo.com']); + assert.deepEqual(bids[0].dealId, 'deal-id'); + }); + it('should set correct native params', () => { + const bid = [ + { + impid: '1', + price: 93.1231, + crid: '12312312', + native: { + assets: [ + { + data: null, + id: 0, + img: null, + required: 0, + title: { text: 'title', len: null }, + video: null, + }, + { + data: null, + id: 2, + img: { type: null, url: 'test.url.com/Files/58345/308185.jpg?bv=1', w: 30, h: 10 }, + required: 0, + title: null, + video: null, + }, + { + data: null, + id: 3, + img: { + type: null, + url: 'test.url.com/Files/58345/308200.jpg?bv=1', + w: 100, + h: 100, + }, + required: 0, + title: null, + video: null, + }, + { + data: { type: null, len: null, value: 'body' }, + id: 4, + img: null, + required: 0, + title: null, + video: null, + }, + { + data: { type: null, len: null, value: 'cta' }, + id: 1, + img: null, + required: 0, + title: null, + video: null, + }, + { + data: { type: null, len: null, value: 'sponsoredBy' }, + id: 5, + img: null, + required: 0, + title: null, + video: null, + }, + ], + link: { url: 'clickUrl', clicktrackers: ['clickTracker1', 'clickTracker2'] }, + imptrackers: ['imptrackers url1', 'imptrackers url2'], + jstracker: 'jstracker', + }, + }, + ]; + const serverResponse = { + body: { + id: null, + bidid: null, + seatbid: [{ bid }], + cur: 'USD', + }, + }; + let bidRequest = { + data: {}, + bids: [{ bidId: 'bidId1' }], + }; + + const result = spec.interpretResponse(serverResponse, bidRequest)[0].native; + const native = bid[0].native; + const assets = native.assets; + assert.deepEqual( + { + clickUrl: native.link.url, + clickTrackers: native.link.clicktrackers, + impressionTrackers: native.imptrackers, + javascriptTrackers: [native.jstracker], + title: assets[0].title.text, + icon: { url: assets[1].img.url, width: assets[1].img.w, height: assets[1].img.h }, + image: { url: assets[2].img.url, width: assets[2].img.w, height: assets[2].img.h }, + body: assets[3].data.value, + cta: assets[4].data.value, + sponsoredBy: assets[5].data.value, + }, + result + ); + }); + it('should return empty when there is no bids in response', () => { + const serverResponse = { + body: { + id: null, + bidid: null, + seatbid: [{ bid: [] }], + cur: 'USD', + }, + }; + let bidRequest = { + data: {}, + bids: [{ bidId: 'bidId1' }], + }; + const result = spec.interpretResponse(serverResponse, bidRequest)[0]; + assert.ok(!result); + }); + + describe('banner', () => { + it('should set ad content on response', () => { + let serverResponse = { + body: { + seatbid: [ + { + bid: [{ impid: '1', adm: '', ext: { prebid: { type: 'banner' } } }], + }, + ], + }, + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { smartadId: 1234 }, + }, + ], + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(bids.length, 1); + assert.equal(bids[0].ad, ''); + assert.equal(bids[0].mediaType, 'banner'); + assert.equal(bids[0].meta.mediaType, 'banner'); + }); + }); + + describe('video', () => { + it('should set vastXml on response', () => { + let serverResponse = { + body: { + seatbid: [ + { + bid: [{ impid: '1', adm: '', ext: { prebid: { type: 'video' } } }], + }, + ], + }, + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { smartadId: 1234 }, + }, + ], + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(bids.length, 1); + assert.equal(bids[0].vastXml, ''); + assert.equal(bids[0].mediaType, 'video'); + assert.equal(bids[0].meta.mediaType, 'video'); + }); + + it('should add renderer for outstream bids', () => { + let serverResponse = { + body: { + seatbid: [ + { + bid: [ + { impid: '1', adm: '', ext: { prebid: { type: 'video' } } }, + { impid: '2', adm: '', ext: { prebid: { type: 'video' } } }, + ], + }, + ], + }, + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { smartadId: 1234 }, + mediaTypes: { + video: { + context: 'outstream', + }, + }, + }, + { + bidId: 'bidId2', + params: { smartadId: 1234 }, + mediaTypes: { + video: { + constext: 'instream', + }, + }, + }, + ], + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.ok(bids[0].renderer); + assert.equal(bids[1].renderer, undefined); + }); + }); + }); +}); diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js new file mode 100644 index 00000000000..a30afd96ad1 --- /dev/null +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -0,0 +1,92 @@ +import { expect } from 'chai'; +import { spec } from 'modules/discoveryBidAdapter.js'; + +describe('DiscoveryDSP:BidAdapterTests', function () { + let bidRequestData = { + bidderCode: 'DiscoveryDSP', + auctionId: 'ff66e39e-4075-4d18-9854-56fde9b879ac', + bidderRequestId: '4fec04e87ad785', + bids: [ + { + bidder: 'DiscoveryDSP', + params: { + token: 'd0f4902b616cc5c38cbe0a08676d0ed9', + }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + adUnitCode: 'regular_iframe', + transactionId: 'd163f9e2-7ecd-4c2c-a3bd-28ceb52a60ee', + sizes: [[300, 250]], + bidId: '276092a19e05eb', + bidderRequestId: '1fadae168708b', + auctionId: 'ff66e39e-4075-4d18-9854-56fde9b879ac', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + }, + ], + }; + let request = []; + + it('DiscoveryDSP:validate_pub_params', function () { + expect( + spec.isBidRequestValid({ + bidder: 'DiscoveryDSP', + params: { + token: ['d0f4902b616cc5c38cbe0a08676d0ed9'], + media: ['test_media'] + }, + }) + ).to.equal(true); + }); + + it('DiscoveryDSP:validate_generated_params', function () { + request = spec.buildRequests(bidRequestData.bids, bidRequestData); + let req_data = JSON.parse(request.data); + expect(req_data.imp).to.have.lengthOf(1); + }); + + it('DiscoveryDSP:validate_response_params', function () { + let tempAdm = '' + tempAdm += '%3Cscr'; + tempAdm += 'ipt%3E'; + tempAdm += '!function(){\"use strict\";function f(t){return(f=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&\"function\"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?\"symbol\":typeof t})(t)}function l(t){var e=0 - - - - - - - - - 00:00:15 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -` - -const supportedSize = [ - { - size: [300, 250], - s: 100 - }, - { - size: [728, 90], - s: 95 - }, - { - size: [300, 600], - s: 90 - }, - { - size: [160, 600], - s: 88 - }, - { - size: [320, 50], - s: 85 - }, - { - size: [300, 50], - s: 80 - }, - { - size: [970, 250], - s: 75 - }, - { - size: [970, 90], - s: 60 - }, -]; -const bidRequest = [{ - 'bidder': 'districtmDMX', - 'params': { - 'dmxid': 100001, - 'memberid': 100003, - }, - 'userId': { - idl_env: {}, - digitrustid: { - data: { - id: {} - } - }, - id5id: { - uid: '' - }, - pubcid: {}, - tdid: {}, - criteoId: {}, - britepoolid: {}, - intentiqid: {}, - lotamePanoramaId: {}, - parrableId: {}, - netId: {}, - lipb: { - lipbid: {} - }, - - }, - 'adUnitCode': 'div-gpt-ad-12345678-1', - 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '29a28a1bbc8a8d', - 'bidderRequestId': '124b579a136515', - 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf' -}]; - -const bidRequestVideo = [{ - 'bidder': 'districtmDMX', - 'params': { - 'dmxid': 100001, - 'memberid': 100003, - 'video': { - id: 123, - skipppable: true, - playback_method: ['auto_play_sound_off', 'viewport_sound_off'], - mimes: ['application/javascript', - 'video/mp4'], - } - }, - 'mediaTypes': { - video: { - context: 'instream', // or 'outstream' - playerSize: [[640, 480]] - } - }, - 'adUnitCode': 'div-gpt-ad-12345678-1', - 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '29a28a1bbc8a8d', - 'bidderRequestId': '124b579a136515', - 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf' -}]; -const bidRequestNoCoppa = [{ - 'bidder': 'districtmDMX', - 'params': { - 'dmxid': 100001, - 'memberid': 100003 - }, - 'adUnitCode': 'div-gpt-ad-12345678-1', - 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '29a28a1bbc8a8d', - 'bidderRequestId': '124b579a136515', - 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf' -}]; -const bidderRequest = { - 'bidderCode': 'districtmDMX', - 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf', - 'bidderRequestId': '124b579a136515', - 'bids': [{ - 'bidder': 'districtmDMX', - 'params': { - 'dmxid': 100001, - 'memberid': 100003, - }, - 'adUnitCode': 'div-gpt-ad-12345678-1', - 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '29a28a1bbc8a8d', - 'bidderRequestId': '124b579a136515', - 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf' - }], - 'auctionStart': 1529511035677, - 'timeout': 700, - 'uspConsent': '1NY', - 'gdprConsent': { - 'consentString': 'BOPqNzUOPqNzUAHABBAAA5AAAAAAAA', - 'vendorData': { - 'metadata': 'BOPqNzUOPqNzUAHABBAAA5AAAAAAAA', - 'hasGlobalScope': false, - 'gdprApplies': true, - 'purposeConsents': { - '1': false, - '2': false, - '3': false, - '4': false, - '5': false - }, - 'vendorConsents': { - '1': false, - '2': false, - '3': false, - '4': false, - '6': false, - '7': false, - '8': false, - '9': false, - '10': false, - '11': false, - '12': false, - '13': false, - '14': false, - '15': false, - '16': false, - '17': false, - '18': false, - '19': false, - '20': false, - '21': false, - '22': false, - '23': false, - '24': false, - '25': false, - '26': false, - '27': false, - '28': false, - '29': false, - '30': false, - '31': false, - '32': false, - '33': false, - '34': false, - '35': false, - '36': false, - '37': false, - '38': false, - '39': false, - '40': false, - '41': false, - '42': false, - '43': false, - '44': false, - '45': false, - '46': false, - '47': false, - '48': false, - '49': false, - '50': false, - '51': false, - '52': false, - '53': false, - '55': false, - '56': false, - '57': false, - '58': false, - '59': false, - '60': false, - '61': false, - '62': false, - '63': false, - '64': false, - '65': false, - '66': false, - '67': false, - '68': false, - '69': false, - '70': false, - '71': false, - '72': false, - '73': false, - '74': false, - '75': false, - '76': false, - '77': false, - '78': false, - '79': false, - '80': false, - '81': false, - '82': false, - '83': false, - '84': false, - '85': false, - '86': false, - '87': false, - '88': false, - '89': false, - '90': false, - '91': false, - '92': false, - '93': false, - '94': false, - '95': false, - '97': false, - '98': false, - '100': false, - '101': false, - '102': false, - '104': false, - '105': false, - '108': false, - '109': false, - '110': false, - '111': false, - '112': false, - '113': false, - '114': false, - '115': false, - '119': false, - '120': false, - '122': false, - '124': false, - '125': false, - '126': false, - '127': false, - '128': false, - '129': false, - '130': false, - '131': false, - '132': false, - '133': false, - '134': false, - '136': false, - '138': false, - '139': false, - '140': false, - '141': false, - '142': false, - '143': false, - '144': false, - '145': false, - '147': false, - '148': false, - '149': false, - '150': false, - '151': false, - '152': false, - '153': false, - '154': false, - '155': false, - '156': false, - '157': false, - '158': false, - '159': false, - '160': false, - '161': false, - '162': false, - '163': false, - '164': false, - '165': false, - '167': false, - '168': false, - '169': false, - '170': false, - '171': false, - '173': false, - '174': false, - '175': false, - '177': false, - '178': false, - '179': false, - '180': false, - '182': false, - '183': false, - '184': false, - '185': false, - '188': false, - '189': false, - '190': false, - '191': false, - '192': false, - '193': false, - '194': false, - '195': false, - '197': false, - '198': false, - '199': false, - '200': false, - '201': false, - '202': false, - '203': false, - '205': false, - '206': false, - '208': false, - '209': false, - '210': false, - '211': false, - '212': false, - '213': false, - '214': false, - '215': false, - '216': false, - '217': false, - '218': false, - '223': false, - '224': false, - '225': false, - '226': false, - '227': false, - '228': false, - '229': false, - '230': false, - '231': false, - '232': false, - '234': false, - '235': false, - '236': false, - '237': false, - '238': false, - '239': false, - '240': false, - '241': false, - '242': false, - '244': false, - '245': false, - '246': false, - '248': false, - '249': false, - '250': false, - '251': false, - '252': false, - '253': false, - '254': false, - '255': false, - '256': false, - '257': false, - '258': false, - '259': false, - '260': false, - '261': false, - '262': false, - '263': false, - '264': false, - '265': false, - '266': false, - '269': false, - '270': false, - '272': false, - '273': false, - '274': false, - '275': false, - '276': false, - '277': false, - '278': false, - '279': false, - '280': false, - '281': false, - '282': false, - '284': false, - '285': false, - '288': false, - '289': false, - '290': false, - '291': false, - '294': false, - '295': false, - '297': false, - '299': false, - '301': false, - '302': false, - '303': false, - '304': false, - '308': false, - '309': false, - '310': false, - '311': false, - '312': false, - '314': false, - '315': false, - '316': false, - '317': false, - '318': false, - '319': false, - '320': false, - '323': false, - '325': false, - '326': false, - '328': false, - '329': false, - '330': false, - '331': false, - '333': false, - '337': false, - '339': false, - '341': false, - '343': false, - '344': false, - '345': false, - '347': false, - '349': false, - '350': false, - '351': false, - '354': false, - '358': false, - '359': false, - '360': false, - '361': false, - '368': false, - '369': false, - '371': false, - '373': false, - '376': false, - '377': false, - '378': false, - '380': false, - '382': false, - '384': false, - '385': false, - '387': false, - '388': false, - '389': false, - '390': false, - '391': false, - '398': false, - '400': false, - '402': false, - '403': false, - '404': false, - '413': false, - '415': false, - '421': false, - '422': false - } - }, - 'gdprApplies': true - }, - 'start': 1529511035686, - 'doneCbCallCount': 0 -}; - -const bidderRequestNoCoppa = { - 'bidderCode': 'districtmDMX', - 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf', - 'bidderRequestId': '124b579a136515', - 'bids': [{ - 'bidder': 'districtmDMX', - 'params': { - 'dmxid': 100001, - 'memberid': 100003, - }, - 'adUnitCode': 'div-gpt-ad-12345678-1', - 'transactionId': 'f6d13fa6-ebc1-41ac-9afa-d8171d22d2c2', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '29a28a1bbc8a8d', - 'bidderRequestId': '124b579a136515', - 'auctionId': '3d62f2d3-56a2-4991-888e-f7754619ddcf' - }], - 'auctionStart': 1529511035677, - 'timeout': 700, - 'start': 1529511035686, - 'doneCbCallCount': 0 -}; - -const responses = { - 'body': { - 'id': '1f45b37c-5298-4934-b517-4d911aadabfd', - 'cur': 'USD', - 'seatbid': [{ - 'bid': [{ - 'id': '29a28a1bbc8a8d', - 'impid': '29a28a1bbc8a8d', - 'price': '6.42', - 'adm': '
' - }] - }] - }, - 'headers': {} -}; - -const responsesNegative = { - 'body': { - 'id': '1f45b37c-5298-4934-b517-4d911aadabfd', - 'cur': 'USD', - 'seatbid': [{ - 'bid': [{ - 'id': '29a28a1bbc8a8d', - 'impid': '29a28a1bbc8a8d', - 'price': '-0.10', - 'adm': '
' - }] - }] - }, - 'headers': {} -}; - -const emptyResponse = { body: {} }; -const emptyResponseSeatBid = { body: { seatbid: [] } }; - -describe('DistrictM Adaptor', function () { - const districtm = spec; - describe('verification of upto5', function () { - it('upto5 function should always break 12 imps into 3 request same for 15', function () { - expect(upto5([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], bidRequest, bidderRequest, 'https://google').length).to.be.equal(3) - expect(upto5([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], bidRequest, bidderRequest, 'https://google').length).to.be.equal(3) - }) - }) - - describe('test vast tag', function () { - it('img tag should not be present', function () { - expect(cleanVast(sample_vast).indexOf('img') !== -1).to.be.equal(false) - }) - }) - describe('Test getApi function', function () { - const data = { - api: [1] - } - it('Will return 1 for vpaid version 1', function () { - expect(getApi(data)[0]).to.be.equal(1) - }) - it('Will return 2 for vpaid default', function () { - expect(getApi({})[0]).to.be.equal(2) - }) - }) - - describe('Test cleanSizes function', function () { - it('sequence will be respected', function () { - expect(cleanSizes(bidderRequest.bids[0].sizes).toString()).to.be.equal('300,250,300,600') - }) - it('sequence will be respected', function () { - expect(cleanSizes([[728, 90], [970, 90], [300, 600], [320, 50]]).toString()).to.be.equal('728,90,320,50,300,600,970,90') - }) - }) - - describe('Test getPlaybackmethod function', function () { - it('getPlaybackmethod will return 2', function () { - expect(getPlaybackmethod([])[0]).to.be.equal(2) - }) - it('getPlaybackmethod will return 6', function () { - expect(getPlaybackmethod(['viewport_sound_off'])[0]).to.be.equal(6) - }) - }) - - describe('Test getProtocols function', function () { - it('getProtocols will return 3', function () { - expect(getProtocols({ protocols: [3] })[0]).to.be.equal(3) - }) - it('getProtocols will return 6', function () { - expect(_.isEqual(getProtocols({}), [2, 3, 5, 6, 7, 8])).to.be.equal(true) - }) - }) - - describe('All needed functions are available', function () { - it(`isBidRequestValid is present and type function`, function () { - expect(districtm.isBidRequestValid).to.exist.and.to.be.a('function') - }); - - it(`BuildRequests is present and type function`, function () { - expect(districtm.buildRequests).to.exist.and.to.be.a('function') - }); - - it(`interpretResponse is present and type function`, function () { - expect(districtm.interpretResponse).to.exist.and.to.be.a('function') - }); - - it(`getUserSyncs is present and type function`, function () { - expect(districtm.getUserSyncs).to.exist.and.to.be.a('function') - }); - }); - - describe(`these properties are available or not`, function () { - it(`code should have a value of districtmDMX`, function () { - expect(districtm.code).to.be.equal('districtmDMX'); - }); - - it(`timeout should not be defined`, function () { - expect(districtm.onTimeout).to.be.an('undefined'); - }); - }); - - describe(`isBidRequestValid test response`, function () { - let params = { - dmxid: 10001, // optional - memberid: 10003, - }; - it(`function should return true`, function () { - expect(districtm.isBidRequestValid({ params })).to.be.equal(true); - }); - it(`function should return false`, function () { - expect(districtm.isBidRequestValid({ params: {} })).to.be.equal(false); - }); - it(`expect to have memberid`, function () { - expect(params).to.have.property('memberid'); - }); - }); - - describe(`getUserSyncs test usage`, function () { - it(`return value should be an array`, function () { - expect(districtm.getUserSyncs({ iframeEnabled: true })).to.be.an('array'); - }); - it(`array should have only one object and it should have a property type = 'iframe'`, function () { - expect(districtm.getUserSyncs({ iframeEnabled: true }).length).to.be.equal(1); - let [userSync] = districtm.getUserSyncs({ iframeEnabled: true }); - expect(userSync).to.have.property('type'); - expect(userSync.type).to.be.equal('iframe'); - }); - }); - - describe(`buildRequests test usage`, function () { - const buildRequestResults = districtm.buildRequests(bidRequest, bidderRequest); - const buildRequestResultsNoCoppa = districtm.buildRequests(bidRequestNoCoppa, bidderRequestNoCoppa); - it(`the function should return an array`, function () { - expect(buildRequestResults).to.be.an('object'); - }); - it(`contain gdpr consent & ccpa`, function () { - const bidr = JSON.parse(buildRequestResults.data) - expect(bidr.regs.ext.gdpr).to.be.equal(1); - expect(bidr.regs.ext.us_privacy).to.be.equal('1NY'); - expect(bidr.user.ext.consent).to.be.an('string'); - }); - it(`test contain COPPA`, function () { - const bidr = JSON.parse(buildRequestResults.data) - bidr.regs = bidr.regs || {}; - bidr.regs.coppa = 1; - expect(bidr.regs.coppa).to.be.equal(1) - }) - it(`test should not contain COPPA`, function () { - const bidr = JSON.parse(buildRequestResultsNoCoppa.data) - expect(bidr.regs.coppa).to.be.equal(0) - }) - it(`the function should return array length of 1`, function () { - expect(buildRequestResults.data).to.be.a('string'); - }); - }); - - describe('bidRequest Video testing', function () { - const request = districtm.buildRequests(bidRequestVideo, bidRequestVideo); - const data = JSON.parse(request.data) - expect(data instanceof Object).to.be.equal(true) - }) - - describe(`interpretResponse test usage`, function () { - const responseResults = districtm.interpretResponse(responses, { bidderRequest }); - const emptyResponseResults = districtm.interpretResponse(emptyResponse, { bidderRequest }); - const emptyResponseResultsNegation = districtm.interpretResponse(responsesNegative, { bidderRequest }); - const emptyResponseResultsEmptySeat = districtm.interpretResponse(emptyResponseSeatBid, { bidderRequest }); - it(`the function should return an array`, function () { - expect(responseResults).to.be.an('array'); - }); - it(`the function should return array length of 1`, function () { - expect(responseResults.length).to.be.equal(1); - }); - it(`the response return nothing`, function () { - expect(emptyResponseResults.length).to.be.equal(0); - }); - it(`the response seatbid return nothing`, function () { - expect(emptyResponseResultsEmptySeat.length).to.be.equal(0); - }); - - it(`on invalid CPM`, function () { - expect(emptyResponseResultsNegation.length).to.be.equal(0); - }); - }); - - describe(`check validation for id sync gdpr ccpa`, () => { - let allin = spec.getUserSyncs({ iframeEnabled: true }, {}, bidderRequest.gdprConsent, bidderRequest.uspConsent)[0] - let noCCPA = spec.getUserSyncs({ iframeEnabled: true }, {}, bidderRequest.gdprConsent, null)[0] - let noGDPR = spec.getUserSyncs({ iframeEnabled: true }, {}, null, bidderRequest.uspConsent)[0] - let nothing = spec.getUserSyncs({ iframeEnabled: true }, {}, null, null)[0] - - /* - - 'uspConsent': '1NY', - 'gdprConsent': { - 'consentString': 'BOPqNzUOPqNzUAHABBAAA5AAAAAAAA', - */ - it(`gdpr & ccpa should be present`, () => { - expect(allin.url).to.be.equal('https://cdn.districtm.io/ids/index.html?gdpr=BOPqNzUOPqNzUAHABBAAA5AAAAAAAA&ccpa=1NY') - }) - it(`ccpa should be present`, () => { - expect(noGDPR.url).to.be.equal('https://cdn.districtm.io/ids/index.html?ccpa=1NY') - }) - it(`gdpr should be present`, () => { - expect(noCCPA.url).to.be.equal('https://cdn.districtm.io/ids/index.html?gdpr=BOPqNzUOPqNzUAHABBAAA5AAAAAAAA') - }) - it(`gdpr & ccpa shouldn't be present`, () => { - expect(nothing.url).to.be.equal('https://cdn.districtm.io/ids/index.html') - }) - }) - - describe(`Helper function testing`, function () { - const bid = matchRequest('29a28a1bbc8a8d', { bidderRequest }); - const { width, height } = defaultSize(bid); - it(`test matchRequest`, function () { - expect(matchRequest('29a28a1bbc8a8d', { bidderRequest })).to.be.an('object'); - }); - it(`test checkDeepArray`, function () { - expect(_.isEqual(checkDeepArray([728, 90]), [728, 90])).to.be.equal(true); - expect(_.isEqual(checkDeepArray([[728, 90]]), [728, 90])).to.be.equal(true); - expect(_.isEqual(checkDeepArray([[728, 90], [300, 250]]), [728, 90])).to.be.equal(true); - expect(_.isEqual(checkDeepArray([[300, 250], [300, 250]]), [728, 90])).to.be.equal(false); - expect(_.isEqual(checkDeepArray([300, 250]), [300, 250])).to.be.equal(true); - }); - it(`test defaultSize`, function () { - expect(width).to.be.equal(300); - expect(height).to.be.equal(250); - }); - }); -}); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index a92f7145747..6872145710c 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -82,20 +82,65 @@ describe('eids array generation for known sub-modules', function() { }); }); - it('merkleId', function() { + it('merkleId (legacy) - supports single id', function() { const userId = { merkleId: { id: 'some-random-id-value', keyID: 1 } }; const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ source: 'merkleinc.com', + uids: [{ + id: 'some-random-id-value', + atype: 3, + ext: { keyID: 1 } + }] + }); + }); + + it('merkleId supports multiple source providers', function() { + const userId = { + merkleId: [{ + id: 'some-random-id-value', ext: { enc: 1, keyID: 16, idName: 'pamId', ssp: 'ssp1' } + }, { + id: 'another-random-id-value', + ext: { + enc: 1, + idName: 'pamId', + third: 4, + ssp: 'ssp2' + } + }] + } + + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(2); + expect(newEids[0]).to.deep.equal({ + source: 'ssp1.merkleinc.com', uids: [{id: 'some-random-id-value', atype: 3, - ext: { keyID: 1 - }}] + ext: { + enc: 1, + keyID: 16, + idName: 'pamId', + ssp: 'ssp1' + } + }] + }); + expect(newEids[1]).to.deep.equal({ + source: 'ssp2.merkleinc.com', + uids: [{id: 'another-random-id-value', + atype: 3, + ext: { + third: 4, + enc: 1, + idName: 'pamId', + ssp: 'ssp2' + } + }] }); }); @@ -213,18 +258,6 @@ describe('eids array generation for known sub-modules', function() { }); }); - it('NextRollId', function() { - const userId = { - nextrollId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'nextroll.com', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - it('zeotapIdPlus', function() { const userId = { IDP: 'some-random-id-value' @@ -269,6 +302,7 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + it('uid2', function() { const userId = { uid2: {'id': 'Sample_AD_Token'} @@ -283,6 +317,7 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + it('kpuid', function() { const userId = { kpuid: 'Sample_Token' @@ -297,6 +332,22 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + + it('tncid', function() { + const userId = { + tncid: 'TEST_TNCID' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'thenewco.it', + uids: [{ + id: 'TEST_TNCID', + atype: 3 + }] + }); + }); + it('pubProvidedId', function() { const userId = { pubProvidedId: [{ @@ -380,8 +431,77 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + + it('cpexId', () => { + const id = 'some-random-id-value' + const userId = { cpexId: id }; + const [eid] = createEidsArray(userId); + expect(eid).to.deep.equal({ + source: 'czechadid.cz', + uids: [{ id: 'some-random-id-value', atype: 1 }] + }); + }); + + describe('ftrackId', () => { + it('should return the correct EID schema', () => { + expect(createEidsArray({ + ftrackId: { + DeviceID: ['aaa', 'bbb'], + SingleDeviceID: ['ccc', 'ddd'], + HHID: ['eee', 'fff'] + }, + foo: { + bar: 'baz' + }, + lorem: { + ipsum: '' + } + })).to.deep.equal([{ + atype: 1, + id: 'aaa|bbb', + ext: { + DeviceID: 'aaa|bbb', + SingleDeviceID: 'ccc|ddd', + HHID: 'eee|fff' + } + }]); + }); + }); + + describe('imuid', function() { + it('should return the correct EID schema with imuid', function() { + const userId = { + imuid: 'testimuid' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'intimatemerger.com', + uids: [{ + id: 'testimuid', + atype: 1 + }] + }); + }); + + it('should return the correct EID schema with imppid', function() { + const userId = { + imppid: 'imppid-value-imppid-value-imppid-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'ppid.intimatemerger.com', + uids: [{ + id: 'imppid-value-imppid-value-imppid-value', + atype: 1 + }] + }); + }); + }); }); -describe('Negative case', function() { + +describe('Negative case', function () { it('eids array generation for UN-known sub-module', function() { // UnknownCommonId const userId = { diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index 043a8a3709e..d99318b5ddc 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -211,7 +211,9 @@ describe('emx_digital Adapter', function () { 'refererInfo': { 'numIframes': 0, 'reachedTop': true, - 'referer': 'https://example.com/index.html?pbjs_debug=true' + 'page': 'https://example.com/index.html?pbjs_debug=true', + 'domain': 'example.com', + 'ref': 'https://referrer.com' }, 'bids': [{ 'bidder': 'emx_digital', @@ -304,7 +306,7 @@ describe('emx_digital Adapter', function () { request = JSON.parse(request.data); expect(request.site).to.have.property('domain', 'example.com'); expect(request.site).to.have.property('page', 'https://example.com/index.html?pbjs_debug=true'); - expect(request.site).to.have.property('ref', window.top.document.referrer); + expect(request.site).to.have.property('ref', 'https://referrer.com'); }); it('builds correctly formatted request banner object', function () { diff --git a/test/spec/modules/enrichmentFpdModule_spec.js b/test/spec/modules/enrichmentFpdModule_spec.js index 3184349cdf7..acdf873d7a6 100644 --- a/test/spec/modules/enrichmentFpdModule_spec.js +++ b/test/spec/modules/enrichmentFpdModule_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { getRefererInfo } from 'src/refererDetection.js'; -import { initSubmodule, coreStorage } from 'modules/enrichmentFpdModule.js'; +import { processFpd, coreStorage } from 'modules/enrichmentFpdModule.js'; describe('the first party data enrichment module', function() { let width; @@ -52,11 +52,12 @@ describe('the first party data enrichment module', function() { width = 800; height = 500; - let validated = initSubmodule({}, {}); + let validated = processFpd({}, {}).global; - expect(validated.site.ref).to.equal(getRefererInfo().referer); - expect(validated.site.page).to.be.undefined; - expect(validated.site.domain).to.be.undefined; + const {ref, page, domain} = getRefererInfo(); + expect(validated.site.ref).to.equal(ref || undefined); + expect(validated.site.page).to.equal(page || undefined) + expect(validated.site.domain).to.equal(domain || undefined) expect(validated.device).to.deep.equal({ w: 800, h: 500 }); expect(validated.site.keywords).to.be.undefined; }); @@ -66,9 +67,9 @@ describe('the first party data enrichment module', function() { height = 500; canonical.href = 'https://www.subdomain.domain.co.uk/path?query=12345'; - let validated = initSubmodule({}, {}); + let validated = processFpd({}, {}).global; - expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.ref).to.equal(getRefererInfo().ref || undefined); expect(validated.site.page).to.equal('https://www.subdomain.domain.co.uk/path?query=12345'); expect(validated.site.domain).to.equal('subdomain.domain.co.uk'); expect(validated.site.publisher.domain).to.equal('domain.co.uk'); @@ -81,12 +82,8 @@ describe('the first party data enrichment module', function() { height = 500; keywords.content = 'value1,value2,value3'; - let validated = initSubmodule({}, {}); + let validated = processFpd({}, {}).global; - expect(validated.site.ref).to.equal(getRefererInfo().referer); - expect(validated.site.page).to.be.undefined; - expect(validated.site.domain).to.be.undefined; - expect(validated.device).to.deep.equal({ w: 800, h: 500 }); expect(validated.site.keywords).to.equal('value1,value2,value3'); }); @@ -94,12 +91,25 @@ describe('the first party data enrichment module', function() { width = 800; height = 500; - let validated = initSubmodule({}, {device: {w: 1200, h: 700}, site: {ref: 'https://someUrl.com', page: 'test.com'}}); + let validated = processFpd({}, {global: {device: {w: 1200, h: 700}, site: {ref: 'https://someUrl.com', page: 'test.com'}}}).global; expect(validated.site.ref).to.equal('https://someUrl.com'); expect(validated.site.page).to.equal('test.com'); - expect(validated.site.domain).to.be.undefined; expect(validated.device).to.deep.equal({ w: 1200, h: 700 }); expect(validated.site.keywords).to.be.undefined; }); + + it('should store a reference to gpc witin ortb2.regs.ext if it has been enabled', function() { + let validated; + width = 800; + height = 500; + + validated = processFpd({}, {}).global; + expect(validated.regs).to.equal(undefined); + + const globalPrivacyControlStub = sinon.stub(window, 'navigator').value({globalPrivacyControl: true}); + validated = processFpd({}, {}).global; + expect(validated.regs.ext.gpc).to.equal(1); + globalPrivacyControlStub.restore(); + }); }); diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js index 872518f2f27..bb9e6c4fb86 100644 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -153,8 +153,8 @@ describe('eplanning analytics adapter', function () { // Step 10 check that the host to send the ajax request is configurable via options expect(eplAnalyticsAdapter.context.host).to.equal(initOptions.host); - // Step 11 verify that we received 6 events - sinon.assert.callCount(eplAnalyticsAdapter.track, 6); + // Step 11 verify that we received 7 events (6 E-Planning events + 1 Clean.io event) + sinon.assert.callCount(eplAnalyticsAdapter.track, 7); }); }); }); diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index 921c133c5b0..db70f7956d7 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -6,6 +6,7 @@ import {init, getIds} from 'modules/userId/index.js'; import * as utils from 'src/utils.js'; import {hook} from '../../../src/hook.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; +import { makeSlot } from '../integration/faker/googletag.js'; describe('E-Planning Adapter', function () { const adapter = newBidder('spec'); @@ -293,7 +294,9 @@ describe('E-Planning Adapter', function () { const refererUrl = 'https://localhost'; const bidderRequest = { refererInfo: { - referer: refererUrl + page: refererUrl, + domain: 'localhost', + ref: refererUrl, }, gdprConsent: { gdprApplies: 1, @@ -337,12 +340,18 @@ describe('E-Planning Adapter', function () { let getWindowSelfStub; let innerWidth; beforeEach(() => { + $$PREBID_GLOBAL$$.bidderSettings = { + eplanning: { + storageAllowed: true + } + }; sandbox = sinon.sandbox.create(); getWindowSelfStub = sandbox.stub(utils, 'getWindowSelf'); getWindowSelfStub.returns(createWindow(800)); }); afterEach(() => { + $$PREBID_GLOBAL$$.bidderSettings = {}; sandbox.restore(); }); @@ -467,7 +476,7 @@ describe('E-Planning Adapter', function () { it('should return ur parameter with current window url', function () { const ur = spec.buildRequests(bidRequests, bidderRequest).data.ur; - expect(ur).to.equal(bidderRequest.refererInfo.referer); + expect(ur).to.equal(bidderRequest.refererInfo.page); }); it('should return fr parameter when there is a referrer', function () { @@ -641,7 +650,24 @@ describe('E-Planning Adapter', function () { let element; let getBoundingClientRectStub; let sandbox = sinon.sandbox.create(); - let focusStub; + let intersectionObserverStub; + let intersectionCallback; + + function setIntersectionObserverMock(params) { + let fakeIntersectionObserver = (stateChange, options) => { + intersectionCallback = stateChange; + return { + unobserve: (element) => { + return element; + }, + observe: (element) => { + intersectionCallback([{'target': {'id': element.id}, 'isIntersecting': params[element.id].isIntersecting, 'intersectionRatio': params[element.id].ratio, 'boundingClientRect': {'width': params[element.id].width, 'height': params[element.id].height}}]); + }, + }; + }; + + intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver); + } function createElement(id) { element = document.createElement('div'); element.id = id || ADUNIT_CODE_VIEW; @@ -721,6 +747,11 @@ describe('E-Planning Adapter', function () { }); } beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + eplanning: { + storageAllowed: true + } + }; getLocalStorageSpy = sandbox.spy(storage, 'getDataFromLocalStorage'); setDataInLocalStorageSpy = sandbox.spy(storage, 'setDataInLocalStorage'); @@ -728,11 +759,9 @@ describe('E-Planning Adapter', function () { hasLocalStorageStub.returns(true); clock = sandbox.useFakeTimers(); - - focusStub = sandbox.stub(window.top.document, 'hasFocus'); - focusStub.returns(true); }); afterEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; sandbox.restore(); if (document.getElementById(ADUNIT_CODE_VIEW)) { document.body.removeChild(element); @@ -772,6 +801,7 @@ describe('E-Planning Adapter', function () { let respuesta; beforeEach(function () { createElementVisible(); + setIntersectionObserverMock({[ADUNIT_CODE_VIEW]: {'ratio': 1, 'isIntersecting': true, 'width': 200, 'height': 200}}); }); it('when you have a render', function() { respuesta = spec.buildRequests(bidRequests, bidderRequest); @@ -809,6 +839,7 @@ describe('E-Planning Adapter', function () { let respuesta; beforeEach(function () { createElementOutOfView(); + setIntersectionObserverMock({[ADUNIT_CODE_VIEW]: {'ratio': 0, 'isIntersecting': false, 'width': 200, 'height': 200}}); }); it('when you have a render', function() { @@ -834,6 +865,7 @@ describe('E-Planning Adapter', function () { let respuesta; it('should register visibility with more than 50%', function() { createPartiallyVisibleElement(); + setIntersectionObserverMock({[ADUNIT_CODE_VIEW]: {'ratio': 0.6, 'isIntersecting': true, 'width': 200, 'height': 200}}); respuesta = spec.buildRequests(bidRequests, bidderRequest); clock.tick(1005); @@ -842,6 +874,7 @@ describe('E-Planning Adapter', function () { }); it('you should not register visibility with less than 50%', function() { createPartiallyInvisibleElement(); + setIntersectionObserverMock({[ADUNIT_CODE_VIEW]: {'ratio': 0.4, 'isIntersecting': true, 'width': 200, 'height': 200}}); respuesta = spec.buildRequests(bidRequests, bidderRequest); clock.tick(1005); @@ -849,12 +882,29 @@ describe('E-Planning Adapter', function () { expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); }); }); + context('when element id is not equal to adunitcode', function() { + let respuesta; + it('should register visibility with more than 50%', function() { + const code = ADUNIT_CODE_VIEW; + const divId = 'div-gpt-ad-123'; + createPartiallyVisibleElement(divId); + window.googletag.pubads().setSlots([makeSlot({ code, divId })]); + setIntersectionObserverMock({[divId]: {'ratio': 0.6, 'isIntersecting': true, 'width': 200, 'height': 200}}); + + respuesta = spec.buildRequests(bidRequests, bidderRequest); + clock.tick(1005); + + expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(storage.getDataFromLocalStorage(storageIdView)).to.equal('1'); + }); + }); context('when width or height of the element is zero', function() { beforeEach(function () { createElementVisible(); }); it('if the width is zero but the height is within the range', function() { element.style.width = '0px'; + setIntersectionObserverMock({[ADUNIT_CODE_VIEW]: {'ratio': 0.4, 'isIntersecting': true, 'width': 200, 'height': 200}}); spec.buildRequests(bidRequests, bidderRequest) clock.tick(1005); @@ -863,6 +913,7 @@ describe('E-Planning Adapter', function () { }); it('if the height is zero but the width is within the range', function() { element.style.height = '0px'; + setIntersectionObserverMock({[ADUNIT_CODE_VIEW]: {'ratio': 1, 'isIntersecting': true, 'width': 500, 'height': 0}}); spec.buildRequests(bidRequests, bidderRequest) clock.tick(1005); @@ -872,6 +923,7 @@ describe('E-Planning Adapter', function () { it('if both are zero', function() { element.style.height = '0px'; element.style.width = '0px'; + setIntersectionObserverMock({[ADUNIT_CODE_VIEW]: {'ratio': 1, 'isIntersecting': true, 'width': 0, 'height': 0}}); spec.buildRequests(bidRequests, bidderRequest) clock.tick(1005); @@ -879,16 +931,6 @@ describe('E-Planning Adapter', function () { expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); }); }); - context('when tab is inactive', function() { - it('I should not register if it is not in focus', function() { - createElementVisible(); - focusStub.returns(false); - spec.buildRequests(bidRequests, bidderRequest); - clock.tick(1005); - expect(storage.getDataFromLocalStorage(storageIdRender)).to.equal('1'); - expect(storage.getDataFromLocalStorage(storageIdView)).to.equal(null); - }); - }); context('segmentBeginsBeforeTheVisibleRange', function() { it('segmentBeginsBeforeTheVisibleRange', function() { createElementOutOfRange(); @@ -919,7 +961,11 @@ describe('E-Planning Adapter', function () { createElementVisible(ADUNIT_CODE_VIEW); createElementVisible(ADUNIT_CODE_VIEW2); createElementVisible(ADUNIT_CODE_VIEW3); - + setIntersectionObserverMock({ + [ADUNIT_CODE_VIEW]: {'ratio': 1, 'isIntersecting': true, 'width': 200, 'height': 200}, + [ADUNIT_CODE_VIEW2]: {'ratio': 1, 'isIntersecting': true, 'width': 200, 'height': 200}, + [ADUNIT_CODE_VIEW3]: {'ratio': 1, 'isIntersecting': true, 'width': 200, 'height': 200} + }); respuesta = spec.buildRequests(bidRequestMultiple, bidderRequest); clock.tick(1005); [ADUNIT_CODE_VIEW, ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { @@ -932,7 +978,11 @@ describe('E-Planning Adapter', function () { createElementOutOfView(ADUNIT_CODE_VIEW); createElementOutOfView(ADUNIT_CODE_VIEW2); createElementOutOfView(ADUNIT_CODE_VIEW3); - + setIntersectionObserverMock({ + [ADUNIT_CODE_VIEW]: {'ratio': 0, 'isIntersecting': false, 'width': 200, 'height': 200}, + [ADUNIT_CODE_VIEW2]: {'ratio': 0, 'isIntersecting': false, 'width': 200, 'height': 200}, + [ADUNIT_CODE_VIEW3]: {'ratio': 0, 'isIntersecting': false, 'width': 200, 'height': 200} + }); respuesta = spec.buildRequests(bidRequestMultiple, bidderRequest); clock.tick(1005); [ADUNIT_CODE_VIEW, ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { @@ -946,7 +996,11 @@ describe('E-Planning Adapter', function () { createElementVisible(ADUNIT_CODE_VIEW); createElementOutOfView(ADUNIT_CODE_VIEW2); createElementOutOfView(ADUNIT_CODE_VIEW3); - + setIntersectionObserverMock({ + [ADUNIT_CODE_VIEW]: {'ratio': 1, 'isIntersecting': true, 'width': 200, 'height': 200}, + [ADUNIT_CODE_VIEW2]: {'ratio': 0.3, 'isIntersecting': true, 'width': 200, 'height': 200}, + [ADUNIT_CODE_VIEW3]: {'ratio': 0, 'isIntersecting': false, 'width': 200, 'height': 200} + }); respuesta = spec.buildRequests(bidRequestMultiple, bidderRequest); clock.tick(1005); expect(storage.getDataFromLocalStorage('pbsr_' + ADUNIT_CODE_VIEW)).to.equal('6'); @@ -975,7 +1029,7 @@ describe('E-Planning Adapter', function () { sandbox.restore(); }) - it('should add eids to the request', function() { + it('should add eids to the request ', function() { let bidRequests = [validBidView]; const expected_id5id = encodeURIComponent(JSON.stringify({ uid: 'ID5-ZHMOL_IfFSt7_lVYX8rBZc6GH3XMWyPQOBUfr4bm0g!', ext: { linkType: 1 } })); const request = spec.buildRequests(bidRequests, bidderRequest); diff --git a/test/spec/modules/fabrickIdSystem_spec.js b/test/spec/modules/fabrickIdSystem_spec.js index c250c8e5e8b..4f3ed55ec03 100644 --- a/test/spec/modules/fabrickIdSystem_spec.js +++ b/test/spec/modules/fabrickIdSystem_spec.js @@ -53,7 +53,7 @@ describe('Fabrick ID System', function() { } let configParams = Object.assign({}, defaultConfigParams, { refererInfo: { - referer: r, + topmostLocation: r, stack: ['s-0'], canonicalUrl: 'cu-0' } @@ -81,7 +81,7 @@ describe('Fabrick ID System', function() { it('should complete successfully', function() { let configParams = Object.assign({}, defaultConfigParams, { refererInfo: { - referer: 'r-0', + topmostLocation: 'r-0', stack: ['s-0'], canonicalUrl: 'cu-0' } diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 2739654eb5d..fc26d9bc0cf 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -108,7 +108,7 @@ describe('FeedAdAdapter', function () { describe('buildRequests', function () { const bidderRequest = { refererInfo: { - referer: 'the referer' + page: 'the referer' }, some: 'thing' }; @@ -332,7 +332,7 @@ describe('FeedAdAdapter', function () { const referer = 'the referer'; const bidderRequest = { refererInfo: { - referer: referer + page: referer }, some: 'thing' }; diff --git a/test/spec/modules/finativeBidAdapter_spec.js b/test/spec/modules/finativeBidAdapter_spec.js new file mode 100644 index 00000000000..7b3f23d8b9e --- /dev/null +++ b/test/spec/modules/finativeBidAdapter_spec.js @@ -0,0 +1,186 @@ +// jshint esversion: 6, es3: false, node: true +import {assert, expect} from 'chai'; +import {spec} from 'modules/finativeBidAdapter.js'; +import { NATIVE } from 'src/mediaTypes.js'; +import { config } from 'src/config.js'; + +describe('Finative adapter', function () { + let serverResponse, bidRequest, bidResponses; + let bid = { + 'bidder': 'finative', + 'params': { + 'adUnitId': '1uyo' + } + }; + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + assert(spec.isBidRequestValid(bid)); + }); + + it('should return false when AdUnitId is not set', function () { + delete bid.params.adUnitId; + assert.isFalse(spec.isBidRequestValid(bid)); + }); + }); + + describe('buildRequests', function () { + it('should send request with correct structure', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {} + }]; + + let request = spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }); + + assert.equal(request.method, 'POST'); + assert.ok(request.data); + }); + + it('should have default request structure', function () { + let keys = 'site,device,cur,imp,user,regs'.split(','); + let validBidRequests = [{ + bidId: 'bidId', + params: {} + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + let data = Object.keys(request); + + assert.deepEqual(keys, data); + }); + + it('Verify the auction ID', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {}, + auctionId: 'auctionId' + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' }, auctionId: validBidRequests[0].auctionId }).data); + + assert.equal(request.id, validBidRequests[0].auctionId); + }); + + it('Verify the device', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {} + }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.device.ua, navigator.userAgent); + }); + + it('Verify native asset ids', function () { + let validBidRequests = [{ + bidId: 'bidId', + params: {}, + nativeParams: { + body: { + required: true, + len: 350 + }, + image: { + required: true + }, + title: { + required: true + }, + sponsoredBy: { + required: true + }, + cta: { + required: true + }, + icon: { + required: true + } + } + }]; + + let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + + assert.equal(assets[0].id, 1); + assert.equal(assets[1].id, 3); + assert.equal(assets[2].id, 0); + assert.equal(assets[3].id, 2); + assert.equal(assets[4].id, 4); + assert.equal(assets[5].id, 5); + }); + }); + + describe('interpretResponse', function () { + const goodResponse = { + body: { + cur: 'EUR', + id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', + seatbid: [ + { + seat: 'finative', + bid: [{ + adm: { + native: { + assets: [ + {id: 0, title: {text: 'this is a title'}} + ], + imptrackers: ['https://domain.for/imp/tracker?price=${AUCTION_PRICE}'], + link: { + clicktrackers: ['https://domain.for/imp/tracker?price=${AUCTION_PRICE}'], + url: 'https://domain.for/ad/' + } + } + }, + impid: 1, + price: 0.55 + }] + } + ] + } + }; + const badResponse = { body: { + cur: 'EUR', + id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', + seatbid: [] + }}; + + const bidRequest = { + data: {}, + bids: [{ bidId: 'bidId1' }] + }; + + it('should return null if body is missing or empty', function () { + const result = spec.interpretResponse(badResponse, bidRequest); + assert.equal(result.length, 0); + + delete badResponse.body + + const result1 = spec.interpretResponse(badResponse, bidRequest); + assert.equal(result.length, 0); + }); + + it('should return the correct params', function () { + const result = spec.interpretResponse(goodResponse, bidRequest); + const bid = goodResponse.body.seatbid[0].bid[0]; + + assert.deepEqual(result[0].currency, goodResponse.body.cur); + assert.deepEqual(result[0].requestId, bidRequest.bids[0].bidId); + assert.deepEqual(result[0].cpm, bid.price); + assert.deepEqual(result[0].creativeId, bid.crid); + assert.deepEqual(result[0].mediaType, 'native'); + assert.deepEqual(result[0].bidderCode, 'finative'); + }); + + it('should return the correct tracking links', function () { + const result = spec.interpretResponse(goodResponse, bidRequest); + const bid = goodResponse.body.seatbid[0].bid[0]; + const regExpPrice = new RegExp('price=' + bid.price); + + result[0].native.clickTrackers.forEach(function (clickTracker) { + assert.ok(clickTracker.search(regExpPrice) > -1); + }); + + result[0].native.impressionTrackers.forEach(function (impTracker) { + assert.ok(impTracker.search(regExpPrice) > -1); + }); + }); + }); +}); diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js index 76f77505105..cddffc63554 100644 --- a/test/spec/modules/fintezaAnalyticsAdapter_spec.js +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -189,7 +189,8 @@ describe('finteza analytics adapter', function () { expect(url.search.value).to.equal(String(cpm)); expect(url.search.unit).to.equal('usd'); - sinon.assert.callCount(fntzAnalyticsAdapter.track, 1); + // 1 Finteza event + 1 Clean.io event + sinon.assert.callCount(fntzAnalyticsAdapter.track, 2); }); }); diff --git a/test/spec/modules/fledge_spec.js b/test/spec/modules/fledge_spec.js new file mode 100644 index 00000000000..2094ab42438 --- /dev/null +++ b/test/spec/modules/fledge_spec.js @@ -0,0 +1,37 @@ +import { + expect +} from 'chai'; +import * as fledge from 'modules/fledgeForGpt.js'; + +const CODE = 'sampleBidder'; +const AD_UNIT_CODE = 'mock/placement'; + +describe('fledgeForGpt module', function() { + let nextFnSpy; + fledge.init({enabled: true}) + + const bidRequest = { + adUnitCode: AD_UNIT_CODE, + bids: [{ + bidId: '1', + bidder: CODE, + auctionId: 'first-bid-id', + adUnitCode: AD_UNIT_CODE, + transactionId: 'au', + }] + }; + const fledgeAuctionConfig = { + bidId: '1', + } + + describe('addComponentAuctionHook', function() { + beforeEach(function() { + nextFnSpy = sinon.spy(); + }); + + it('should call next() when a proper bidrequest and fledgeAuctionConfig are provided', function() { + fledge.addComponentAuctionHook(nextFnSpy, bidRequest, fledgeAuctionConfig); + expect(nextFnSpy.called).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/fpdModule_spec.js b/test/spec/modules/fpdModule_spec.js index c2a6c41835e..498bed29243 100644 --- a/test/spec/modules/fpdModule_spec.js +++ b/test/spec/modules/fpdModule_spec.js @@ -1,80 +1,52 @@ import {expect} from 'chai'; -import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; import {getRefererInfo} from 'src/refererDetection.js'; -import {init, registerSubmodules} from 'modules/fpdModule/index.js'; +import {processFpd, registerSubmodules, startAuctionHook, reset} from 'modules/fpdModule/index.js'; import * as enrichmentModule from 'modules/enrichmentFpdModule.js'; import * as validationModule from 'modules/validationFpdModule/index.js'; -let enrichments = { - name: 'enrichments', - queue: 2, - init: enrichmentModule.initSubmodule -}; -let validations = { - name: 'validations', - queue: 1, - init: validationModule.initSubmodule -}; - describe('the first party data module', function () { - let ortb2 = { - device: { - h: 911, - w: 1733 - }, - user: { - data: [{ - segment: [{ - id: 'foo' - }], - name: 'bar', - ext: 'string' - }] - }, - site: { - content: { - data: [{ - segment: [{ - id: 'test' - }], - name: 'content', - ext: { - foo: 'bar' - } - }] - } + afterEach(function () { + config.resetConfig(); + }); + + describe('startAuctionHook', () => { + const mockFpd = { + global: {key: 'value'}, + bidder: {A: {bkey: 'bvalue'}} } - }; + beforeEach(() => { + reset(); + }); - let conf = { - device: { - h: 500, - w: 750 - }, - user: { - keywords: 'test1, test2', - gender: 'f', - data: [{ - segment: [{ - id: 'test' - }], - name: 'alt' - }] - }, - site: { - ref: 'domain.com', - page: 'www.domain.com/test', - ext: { - data: { - inventory: ['first'] + it('should run ortb2Fragments through fpd submodules', () => { + registerSubmodules({ + name: 'test', + processFpd: function () { + return mockFpd; } - } - } - }; + }); + const req = {ortb2Fragments: {}}; + return new Promise((resolve) => startAuctionHook(resolve, req)) + .then(() => { + expect(req.ortb2Fragments).to.eql(mockFpd); + }) + }); - afterEach(function () { - config.resetConfig(); + it('should work with fpd submodules that return promises', () => { + registerSubmodules({ + name: 'test', + processFpd: function () { + return Promise.resolve(mockFpd); + } + }); + const req = {ortb2Fragments: {}}; + return new Promise((resolve) => { + startAuctionHook(resolve, req); + }).then(() => { + expect(req.ortb2Fragments).to.eql(mockFpd); + }); + }); }); describe('first party data intitializing', function () { @@ -87,6 +59,10 @@ describe('the first party data module', function () { let keywords; before(function() { + reset(); + registerSubmodules(enrichmentModule); + registerSubmodules(validationModule); + canonical = document.createElement('link'); canonical.rel = 'canonical'; keywords = document.createElement('meta'); @@ -115,159 +91,77 @@ describe('the first party data module', function () { keywords.name = 'keywords'; }); - it('sets default referer and dimension values to ortb2 data', function () { - registerSubmodules(enrichments); - registerSubmodules(validations); - - let validated; - - width = 1120; - height = 750; - - init(); - - validated = config.getConfig('ortb2'); - expect(validated.site.ref).to.equal(getRefererInfo().referer); - expect(validated.site.page).to.be.undefined; - expect(validated.site.domain).to.be.undefined; - expect(validated.device).to.deep.equal({w: 1120, h: 750}); - expect(validated.site.keywords).to.be.undefined; - }); - - it('sets page and domain values to ortb2 data if canonical link exists', function () { - let validated; - - canonical.href = 'https://www.domain.com/path?query=12345'; - - init(); - - validated = config.getConfig('ortb2'); - expect(validated.site.ref).to.equal(getRefererInfo().referer); - expect(validated.site.page).to.equal('https://www.domain.com/path?query=12345'); - expect(validated.site.domain).to.equal('domain.com'); - expect(validated.device).to.deep.to.equal({w: 1120, h: 750}); - expect(validated.site.keywords).to.be.undefined; - }); - - it('sets keyword values to ortb2 data if keywords meta exists', function () { - let validated; - - keywords.content = 'value1,value2,value3'; - - init(); - - validated = config.getConfig('ortb2'); - expect(validated.site.ref).to.equal(getRefererInfo().referer); - expect(validated.site.page).to.be.undefined; - expect(validated.site.domain).to.be.undefined; - expect(validated.device).to.deep.to.equal({w: 1120, h: 750}); - expect(validated.site.keywords).to.equal('value1,value2,value3'); - }); - - it('only sets values that do not exist in ortb2 config', function () { - let validated; - - config.setConfig({ortb2: {site: {ref: 'https://testpage.com', domain: 'newDomain.com'}}}); - - init(); - - validated = config.getConfig('ortb2'); - expect(validated.site.ref).to.equal('https://testpage.com'); - expect(validated.site.page).to.be.undefined; - expect(validated.site.domain).to.equal('newDomain.com'); - expect(validated.device).to.deep.to.equal({w: 1120, h: 750}); - expect(validated.site.keywords).to.be.undefined; - }); - it('filters ortb2 data that is set', function () { - let validated; - let conf = { - ortb2: { - user: { - data: {}, - gender: 'f', - age: 45 - }, - site: { - content: { - data: [{ - segment: { - test: 1 - }, - name: 'foo' + const global = { + user: { + data: {}, + gender: 'f', + age: 45 + }, + site: { + content: { + data: [{ + segment: { + test: 1 + }, + name: 'foo' + }, { + segment: [{ + id: 'test' }, { - segment: [{ - id: 'test' - }, { - id: 3 - }], - name: 'bar' - }] - } - }, - device: { - w: 1, - h: 1 + id: 3 + }], + name: 'bar' + }] } + }, + device: { + w: 1, + h: 1 } }; - config.setConfig(conf); canonical.href = 'https://www.domain.com/path?query=12345'; width = 1120; height = 750; - init(); - - validated = config.getConfig('ortb2'); - expect(validated.site.ref).to.equal(getRefererInfo().referer); - expect(validated.site.page).to.equal('https://www.domain.com/path?query=12345'); - expect(validated.site.domain).to.equal('domain.com'); - expect(validated.site.content.data).to.deep.equal([{segment: [{id: 'test'}], name: 'bar'}]); - expect(validated.user.data).to.be.undefined; - expect(validated.device).to.deep.to.equal({w: 1, h: 1}); - expect(validated.site.keywords).to.be.undefined; + return processFpd({global}).then(({global: validated}) => { + expect(validated.site.ref).to.equal(getRefererInfo().ref || undefined); + expect(validated.site.page).to.equal('https://www.domain.com/path?query=12345'); + expect(validated.site.domain).to.equal('domain.com'); + expect(validated.site.content.data).to.deep.equal([{segment: [{id: 'test'}], name: 'bar'}]); + expect(validated.user.data).to.be.undefined; + expect(validated.device).to.deep.to.equal({w: 1, h: 1}); + expect(validated.site.keywords).to.be.undefined; + }); }); it('should not overwrite existing data with default settings', function () { - let validated; - let conf = { - ortb2: { - site: { - ref: 'https://referer.com' - } + const global = { + site: { + ref: 'https://referer.com' } }; - config.setConfig(conf); - - init(); - - validated = config.getConfig('ortb2'); - expect(validated.site.ref).to.equal('https://referer.com'); + return processFpd({global}).then(({global: validated}) => { + expect(validated.site.ref).to.equal('https://referer.com'); + }); }); it('should allow overwrite default data with setConfig', function () { - let validated; - let conf = { - ortb2: { - site: { - ref: 'https://referer.com' - } + const global = { + site: { + ref: 'https://referer.com' } }; - config.setConfig(conf); - - init(); - - validated = config.getConfig('ortb2'); - expect(validated.site.ref).to.equal('https://referer.com'); + return processFpd({global}).then(({global: validated}) => { + expect(validated.site.ref).to.equal('https://referer.com'); + }); }); it('should filter all data', function () { - let validated; - let conf = { + let global = { imp: [], site: { name: 123, @@ -297,20 +191,14 @@ describe('the first party data module', function () { adServerCurrency: 'USD' } }; - config.setConfig({'firstPartyData': {skipEnrichments: true}}); - - config.setConfig({ortb2: conf}); - - init(); - - validated = config.getConfig('ortb2'); - expect(validated).to.deep.equal({}); + return processFpd({global}).then(({global: validated}) => { + expect(validated).to.deep.equal({}); + }); }); it('should add enrichments but not alter any arbitrary ortb2 data', function () { - let validated; - let conf = { + let global = { site: { ext: { data: { @@ -327,89 +215,70 @@ describe('the first party data module', function () { }, cur: ['USD'] }; - - config.setConfig({ortb2: conf}); - - init(); - - validated = config.getConfig('ortb2'); - expect(validated.site.ref).to.equal(getRefererInfo().referer); - expect(validated.site.ext.data).to.deep.equal({inventory: ['value1']}); - expect(validated.user.ext.data).to.deep.equal({visitor: ['value2']}); - expect(validated.cur).to.deep.equal(['USD']); + return processFpd({global}).then(({global: validated}) => { + expect(validated.site.ref).to.equal(getRefererInfo().referer); + expect(validated.site.ext.data).to.deep.equal({inventory: ['value1']}); + expect(validated.user.ext.data).to.deep.equal({visitor: ['value2']}); + expect(validated.cur).to.deep.equal(['USD']); + }) }); it('should filter bidderConfig data', function () { - let validated; - let conf = { - bidders: ['bidderA', 'bidderB'], - config: { - ortb2: { - site: { - keywords: 'other', - ref: 'https://domain.com' - }, - user: { - keywords: 'test', - data: [{ - segment: [{id: 4}], - name: 't' - }] - } + let bidder = { + bidderA: { + site: { + keywords: 'other', + ref: 'https://domain.com' + }, + user: { + keywords: 'test', + data: [{ + segment: [{id: 4}], + name: 't' + }] } } }; - config.setBidderConfig(conf); - - init(); - - validated = config.getBidderConfig(); - expect(validated.bidderA.ortb2).to.not.be.undefined; - expect(validated.bidderA.ortb2.user.data).to.be.undefined; - expect(validated.bidderA.ortb2.user.keywords).to.equal('test'); - expect(validated.bidderA.ortb2.site.keywords).to.equal('other'); - expect(validated.bidderA.ortb2.site.ref).to.equal('https://domain.com'); + return processFpd({bidder}).then(({bidder: validated}) => { + expect(validated.bidderA).to.not.be.undefined; + expect(validated.bidderA.user.data).to.be.undefined; + expect(validated.bidderA.user.keywords).to.equal('test'); + expect(validated.bidderA.site.keywords).to.equal('other'); + expect(validated.bidderA.site.ref).to.equal('https://domain.com'); + }) }); it('should not filter bidderConfig data as it is valid', function () { - let validated; - let conf = { - bidders: ['bidderA', 'bidderB'], - config: { - ortb2: { - site: { - keywords: 'other', - ref: 'https://domain.com' - }, - user: { - keywords: 'test', - data: [{ - segment: [{id: 'data1_id'}], - name: 'data1' - }] - } + let bidder = { + bidderA: { + site: { + keywords: 'other', + ref: 'https://domain.com' + }, + user: { + keywords: 'test', + data: [{ + segment: [{id: 'data1_id'}], + name: 'data1' + }] } } }; - config.setBidderConfig(conf); - - init(); - - validated = config.getBidderConfig(); - expect(validated.bidderA.ortb2).to.not.be.undefined; - expect(validated.bidderA.ortb2.user.data).to.deep.equal([{segment: [{id: 'data1_id'}], name: 'data1'}]); - expect(validated.bidderA.ortb2.user.keywords).to.equal('test'); - expect(validated.bidderA.ortb2.site.keywords).to.equal('other'); - expect(validated.bidderA.ortb2.site.ref).to.equal('https://domain.com'); + return processFpd({bidder}).then(({bidder: validated}) => { + expect(validated.bidderA).to.not.be.undefined; + expect(validated.bidderA.user.data).to.deep.equal([{segment: [{id: 'data1_id'}], name: 'data1'}]); + expect(validated.bidderA.user.keywords).to.equal('test'); + expect(validated.bidderA.site.keywords).to.equal('other'); + expect(validated.bidderA.site.ref).to.equal('https://domain.com'); + }); }); it('should not set default values if skipEnrichments is turned on', function () { - let validated; config.setConfig({'firstPartyData': {skipEnrichments: true}}); - let conf = { + let global = { site: { keywords: 'other' }, @@ -420,26 +289,20 @@ describe('the first party data module', function () { name: 'data1' }] } - } - ; - - config.setConfig({ortb2: conf}); - - init(); + }; - validated = config.getConfig(); - expect(validated.ortb2).to.not.be.undefined; - expect(validated.ortb2.device).to.be.undefined; - expect(validated.ortb2.site.ref).to.be.undefined; - expect(validated.ortb2.site.page).to.be.undefined; - expect(validated.ortb2.site.domain).to.be.undefined; + return processFpd({global}).then(({global: validated}) => { + expect(validated.device).to.be.undefined; + expect(validated.site.ref).to.be.undefined; + expect(validated.site.page).to.be.undefined; + expect(validated.site.domain).to.be.undefined; + }); }); it('should not validate ortb2 data if skipValidations is turned on', function () { - let validated; config.setConfig({'firstPartyData': {skipValidations: true}}); - let conf = { + let global = { site: { keywords: 'other' }, @@ -449,16 +312,11 @@ describe('the first party data module', function () { segment: [{id: 'nonfiltered'}] }] } - } - ; - - config.setConfig({ortb2: conf}); - - init(); + }; - validated = config.getConfig(); - expect(validated.ortb2).to.not.be.undefined; - expect(validated.ortb2.user.data).to.deep.equal([{segment: [{id: 'nonfiltered'}]}]); + return processFpd({global}).then(({global: validated}) => { + expect(validated.user.data).to.deep.equal([{segment: [{id: 'nonfiltered'}]}]); + }); }); }); }); diff --git a/test/spec/modules/ftrackIdSystem_spec.js b/test/spec/modules/ftrackIdSystem_spec.js index f2b0456a849..2c3f3f8576c 100644 --- a/test/spec/modules/ftrackIdSystem_spec.js +++ b/test/spec/modules/ftrackIdSystem_spec.js @@ -2,6 +2,10 @@ import { ftrackIdSubmodule } from 'modules/ftrackIdSystem.js'; import * as utils from 'src/utils.js'; import { uspDataHandler } from 'src/adapterManager.js'; import { loadExternalScript } from 'src/adloader.js'; +import { getGlobal } from 'src/prebidGlobal.js'; +import { init, setSubmoduleRegistry } from 'modules/userId/index.js'; +import {createEidsArray} from 'modules/userId/eids.js'; +import {config} from 'src/config.js'; let expect = require('chai').expect; let server; @@ -147,7 +151,7 @@ describe('FTRACK ID System', () => { }); it(`should be the only method that gets a new ID aka hits the D9 endpoint`, () => { - ftrackIdSubmodule.getId(configMock, null, null).callback(); + ftrackIdSubmodule.getId(configMock, null, null).callback(() => {}); expect(loadExternalScript.called).to.be.ok; expect(loadExternalScript.args[0][0]).to.deep.equal('https://d9.flashtalking.com/d9core'); loadExternalScript.resetHistory(); @@ -168,7 +172,7 @@ describe('FTRACK ID System', () => { it(`should use default IDs if config.params.id is not populated`, () => { let configMock1 = JSON.parse(JSON.stringify(configMock)); delete configMock1.params.ids; - ftrackIdSubmodule.getId(configMock1, null, null).callback(); + ftrackIdSubmodule.getId(configMock1, null, null).callback(() => {}); expect(window.D9r).to.have.property('DeviceID', true); expect(window.D9r).to.have.property('SingleDeviceID', true); @@ -181,7 +185,7 @@ describe('FTRACK ID System', () => { configMock1.params.ids['device id'] = 'test device ID'; configMock1.params.ids['single device id'] = 'test single device ID'; configMock1.params.ids['household id'] = 'test household ID'; - ftrackIdSubmodule.getId(configMock1, null, null).callback(); + ftrackIdSubmodule.getId(configMock1, null, null).callback(() => {}); expect(window.D9r).to.not.have.property('DeviceID'); expect(window.D9r).to.not.have.property('SingleDeviceID'); @@ -193,7 +197,7 @@ describe('FTRACK ID System', () => { configMock1.params.ids['device id'] = false; configMock1.params.ids['single device id'] = false; configMock1.params.ids['household id'] = false; - ftrackIdSubmodule.getId(configMock1, null, null).callback(); + ftrackIdSubmodule.getId(configMock1, null, null).callback(() => {}); expect(window.D9r).to.not.have.property('DeviceID'); expect(window.D9r).to.not.have.property('SingleDeviceID'); @@ -203,7 +207,7 @@ describe('FTRACK ID System', () => { it(`- only device id`, () => { let configMock1 = JSON.parse(JSON.stringify(configMock)); delete configMock1.params.ids['single device id']; - ftrackIdSubmodule.getId(configMock1, null, null).callback(); + ftrackIdSubmodule.getId(configMock1, null, null).callback(() => {}); expect(window.D9r).to.have.property('DeviceID', true); expect(window.D9r).to.not.have.property('SingleDeviceID'); @@ -213,7 +217,7 @@ describe('FTRACK ID System', () => { it(`- only single device id`, () => { let configMock1 = JSON.parse(JSON.stringify(configMock)); delete configMock1.params.ids['device id']; - ftrackIdSubmodule.getId(configMock1, null, null).callback(); + ftrackIdSubmodule.getId(configMock1, null, null).callback(() => {}); expect(window.D9r).to.not.have.property('DeviceID'); expect(window.D9r).to.have.property('SingleDeviceID', true); @@ -225,7 +229,7 @@ describe('FTRACK ID System', () => { delete configMock1.params.ids['device id']; delete configMock1.params.ids['single device id']; configMock1.params.ids['household id'] = true; - ftrackIdSubmodule.getId(configMock1, null, null).callback(); + ftrackIdSubmodule.getId(configMock1, null, null).callback(() => {}); expect(window.D9r).to.not.have.property('DeviceID'); expect(window.D9r).to.not.have.property('SingleDeviceID'); @@ -243,7 +247,7 @@ describe('FTRACK ID System', () => { expect(window.localStorage.getItem('ftrack-rtd')).to.not.be.ok; expect(window.localStorage.getItem('ftrack-rtd_exp')).to.not.be.ok; - ftrackIdSubmodule.getId(configMock, consentDataMock, null).callback(); + ftrackIdSubmodule.getId(configMock, consentDataMock, null).callback(() => {}); return new Promise(function(resolve, reject) { window.testTimer = function () { // Sinon fake server is NOT changing the readyState to 4, so instead @@ -271,7 +275,12 @@ describe('FTRACK ID System', () => { describe(`decode() method`, () => { it(`should respond with an object with the key 'ftrackId'`, () => { - expect(ftrackIdSubmodule.decode('value', configMock)).to.deep.equal({ftrackId: 'value'}); + expect(ftrackIdSubmodule.decode('value', configMock)).to.deep.equal({ + ftrackId: { + ext: { 0: 'v', 1: 'a', 2: 'l', 3: 'u', 4: 'e' }, + uid: undefined, + }, + }); }); it(`should not be making requests to retrieve a new ID, it should just be decoding a response`, () => { @@ -298,4 +307,134 @@ describe('FTRACK ID System', () => { expect(ftrackIdSubmodule.extendId(configMock, null, {cache: {id: ''}})).to.deep.equal({cache: {id: ''}}); }); }); + + describe('pbjs "get id" methods', () => { + // The full set of ftrack IDs to test against + let expectedIds = { + HHID: ['household_test_id'], + DeviceID: ['device_test_id'], + SingleDeviceID: ['single_device_test_id'] + }; + // The full config mock + let userSyncConfigMock = { + userSync: { + auctionDelay: 10, + userIds: [{ + name: 'ftrack', + value: { + ftrackId: expectedIds + } + }] + } + }; + // The full eids response + let expectedEids = [{ + id: 'device_test_id', + atype: 1, + ext: { + HHID: expectedIds.HHID.join('|'), + DeviceID: expectedIds.DeviceID.join('|'), + SingleDeviceID: expectedIds.SingleDeviceID.join('|') + } + }]; + + beforeEach(() => { + init(config); + setSubmoduleRegistry([ftrackIdSubmodule]); + }); + + afterEach(() => { + // Reset expectedIds to the default values because some tests overwrite them + expectedIds = { + HHID: ['household_test_id'], + DeviceID: ['device_test_id'], + SingleDeviceID: ['single_device_test_id'] + }; + }); + + describe('pbjs.getUserIdsAsync()', () => { + it('should return the IDs in the correct schema', () => { + config.setConfig(userSyncConfigMock); + + getGlobal().getUserIdsAsync().then(ids => { + expect(ids).to.deep.equal({ + ftrackId: expectedIds + }); + }); + }); + }); + + describe('pbjs.getUserIds()', () => { + it('should return the IDs in the correct schema', () => { + config.setConfig(userSyncConfigMock); + + expect(getGlobal().getUserIds()).to.deep.equal({ + 'ftrackId': expectedIds + }); + }); + }); + + describe('pbjs.getUserIdsAsEids()', () => { + it('should return the correct EIDs schema', () => { + let userSyncConfig = Object.assign({}, userSyncConfigMock); + + config.setConfig(userSyncConfig); + + expect(getGlobal().getUserIdsAsEids()).to.deep.equal(expectedEids); + }); + + describe('by ID type:', () => { + it('- DeviceID', () => { + let userSyncConfig = Object.assign({}, userSyncConfigMock); + + userSyncConfig.userSync.userIds[0].value.ftrackId = { + DeviceID: ['device_test_id'] + }; + + let expectedEidsClone = expectedEids.slice(); + expectedEidsClone[0].ext = { + DeviceID: expectedIds.DeviceID.join('|') + }; + + config.setConfig(userSyncConfig); + + expect(getGlobal().getUserIdsAsEids()).to.deep.equal(expectedEidsClone); + }); + + it('- HHID', () => { + let userSyncConfig = Object.assign({}, userSyncConfigMock); + userSyncConfig.userSync.userIds[0].value.ftrackId = { + HHID: ['household_test_id'] + }; + + let expectedEidsClone = expectedEids.slice(); + expectedEidsClone[0].id = ''; + expectedEidsClone[0].ext = { + HHID: expectedIds.HHID.join('|') + }; + + config.setConfig(userSyncConfig); + + expect(getGlobal().getUserIdsAsEids()).to.deep.equal(expectedEidsClone); + }); + + it('- SingleDeviceID', () => { + let userSyncConfig = Object.assign({}, userSyncConfigMock); + userSyncConfig.userSync.userIds[0].value.ftrackId = { + SingleDeviceID: ['single_device_test_id'] + }; + + let expectedEidsClone = expectedEids.slice(); + expectedEidsClone[0].id = ''; + expectedEidsClone[0].ext = { + SingleDeviceID: expectedIds.SingleDeviceID.join('|') + }; + + config.setConfig(userSyncConfig); + + expect(getGlobal().getUserIdsAsEids()).to.deep.equal(expectedEidsClone); + }); + }); + }); + }) }); diff --git a/test/spec/modules/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js index 8d63a32ef4d..8f8e3f03736 100644 --- a/test/spec/modules/gamoshiBidAdapter_spec.js +++ b/test/spec/modules/gamoshiBidAdapter_spec.js @@ -201,10 +201,6 @@ describe('GamoshiAdapter', () => { it('check if you are in the top frame', () => { expect(helper.getTopFrame()).to.equal(0); }); - - it('verify domain parsing', () => { - expect(helper.getTopWindowDomain('http://www.domain.com')).to.equal('www.domain.com'); - }); }); describe('Is String start with search', () => { @@ -323,12 +319,16 @@ describe('GamoshiAdapter', () => { it('builds request correctly', () => { let bidRequest2 = utils.deepClone(bidRequest); - bidRequest2.refererInfo.referer = 'http://www.test.com/page.html'; + Object.assign(bidRequest2.refererInfo, { + page: 'http://www.test.com/page.html', + domain: 'www.test.com', + ref: 'http://referrer.com' + }) let response = spec.buildRequests([bidRequest], bidRequest2)[0]; expect(response.data.site.domain).to.equal('www.test.com'); expect(response.data.site.page).to.equal('http://www.test.com/page.html'); - expect(response.data.site.ref).to.equal('http://www.test.com/page.html'); + expect(response.data.site.ref).to.equal('http://referrer.com'); expect(response.data.imp.length).to.equal(1); expect(response.data.imp[0].id).to.equal(bidRequest.transactionId); expect(response.data.imp[0].instl).to.equal(0); diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index e74b4e82263..ddd522decab 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -1,22 +1,25 @@ import { deviceAccessHook, - enableAnalyticsHook, - enforcementRules, - getGvlid, - internal, + setEnforcementConfig, + userSyncHook, + userIdHook, makeBidRequestsHook, + validateRules, + enforcementRules, purpose1Rule, purpose2Rule, - setEnforcementConfig, - userIdHook, - userSyncHook, - validateRules + enableAnalyticsHook, + getGvlid, + internal, STRICT_STORAGE_ENFORCEMENT } from 'modules/gdprEnforcement.js'; -import {config} from 'src/config.js'; -import adapterManager, {gdprDataHandler} from 'src/adapterManager.js'; +import { config } from 'src/config.js'; +import adapterManager, { gdprDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import {validateStorageEnforcement} from 'src/storageManager.js'; +import { validateStorageEnforcement } from 'src/storageManager.js'; import * as events from 'src/events.js'; +import 'modules/appnexusBidAdapter.js'; // some tests expect this to be in the adapter registry +import 'src/prebid.js' +import {hook} from '../../../src/hook.js'; describe('gdpr enforcement', function () { let nextFnSpy; @@ -97,10 +100,14 @@ describe('gdpr enforcement', function () { } }; + before(() => { + hook.ready(); + }); + after(function () { - validateStorageEnforcement.getHooks({hook: deviceAccessHook}).remove(); + validateStorageEnforcement.getHooks({ hook: deviceAccessHook }).remove(); $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); - adapterManager.makeBidRequests.getHooks({hook: makeBidRequestsHook}).remove(); + adapterManager.makeBidRequests.getHooks({ hook: makeBidRequestsHook }).remove(); }) describe('deviceAccessHook', function () { @@ -143,13 +150,13 @@ describe('gdpr enforcement', function () { } }); - deviceAccessHook(nextFnSpy); + deviceAccessHook(nextFnSpy, false); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: false } - sinon.assert.calledWith(nextFnSpy, undefined, undefined, undefined, result); + sinon.assert.calledWith(nextFnSpy, false, undefined, undefined, result); }); it('should only check for consent for vendor exceptions when enforcePurpose and enforceVendor are false', function () { @@ -171,8 +178,8 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, 5, 'rubicon'); + deviceAccessHook(nextFnSpy, false, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, false, 5, 'rubicon'); expect(logWarnSpy.callCount).to.equal(0); }); @@ -194,55 +201,8 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); - deviceAccessHook(nextFnSpy, 3, 'rubicon'); - expect(logWarnSpy.callCount).to.equal(1); - }); - - it('should allow device access when enforce vendor but module is vendorless ', function () { - adapterManagerStub.withArgs('pubCommonId').returns(getBidderSpec(1)); - setEnforcementConfig({ - gdpr: { - rules: [{ - purpose: 'storage', - enforcePurpose: true, - enforceVendor: true, - }] - } - }); - let consentData = {} - consentData.vendorData = staticConfig.consentData.getTCData; - consentData.gdprApplies = true; - consentData.apiVersion = 2; - gdprDataHandlerStub.returns(consentData); - - deviceAccessHook(nextFnSpy, 1, 'pubCommonId', 'fpid-module'); - expect(logWarnSpy.callCount).to.equal(0); - }); - - it('should not allow device access if enforce vendor, module is vendorless, but there is no consent for purpose 1', function () { - adapterManagerStub.withArgs('pubCommonId').returns(getBidderSpec(1)); - setEnforcementConfig({ - gdpr: { - rules: [{ - purpose: 'storage', - enforcePurpose: true, - enforceVendor: true, - }] - } - }); - - let consentData = {} - // set consent for purpose 1 to false - const newConsentData = utils.deepClone(staticConfig); - newConsentData.consentData.getTCData.purpose.consents['1'] = false; - consentData.vendorData = newConsentData.consentData.getTCData; - consentData.apiVersion = 2; - consentData.gdprApplies = true; - - gdprDataHandlerStub.returns(consentData); - - deviceAccessHook(nextFnSpy, 1, 'pubCommonId', 'fpid-module'); + deviceAccessHook(nextFnSpy, false, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, false, 3, 'rubicon'); expect(logWarnSpy.callCount).to.equal(1); }); @@ -264,16 +224,16 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, false, 1, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 1, 'appnexus', undefined, result); + sinon.assert.calledWith(nextFnSpy, false, 1, 'appnexus', result); }); - it('should use gvlMapping set by publisher', function () { + it('should use gvlMapping set by publisher', function() { config.setConfig({ 'gvlMapping': { 'appnexus': 4 @@ -295,17 +255,17 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, false, 1, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', undefined, result); + sinon.assert.calledWith(nextFnSpy, false, 4, 'appnexus', result); config.resetConfig(); }); - it('should use gvl id of alias and not of parent', function () { + it('should use gvl id of alias and not of parent', function() { let curBidderStub = sinon.stub(config, 'getCurrentBidder'); curBidderStub.returns('appnexus-alias'); adapterManager.aliasBidAdapter('appnexus', 'appnexus-alias'); @@ -330,16 +290,43 @@ describe('gdpr enforcement', function () { consentData.apiVersion = 2; gdprDataHandlerStub.returns(consentData); - deviceAccessHook(nextFnSpy, 1, 'appnexus'); + deviceAccessHook(nextFnSpy, false, 1, 'appnexus'); expect(nextFnSpy.calledOnce).to.equal(true); let result = { hasEnforcementHook: true, valid: true } - sinon.assert.calledWith(nextFnSpy, 4, 'appnexus', undefined, result); + sinon.assert.calledWith(nextFnSpy, false, 4, 'appnexus', result); config.resetConfig(); curBidderStub.restore(); }); + + it(`should mark module as vendorless for rule validation when isVendorless = true and ${STRICT_STORAGE_ENFORCEMENT} is set`, () => { + setEnforcementConfig({ + [STRICT_STORAGE_ENFORCEMENT]: true + }); + let consentData = { + vendorData: staticConfig.consentData.getTCData, + gdprApplies: true + } + gdprDataHandlerStub.returns(consentData); + const validate = sinon.stub().callsFake(() => true); + deviceAccessHook(nextFnSpy, true, 123, 'mockModule', undefined, {validate}); + expect(validate.args[0][4]('mockModule')).to.be.true; + }); + + it(`should not enforce consent for vendorless modules if ${STRICT_STORAGE_ENFORCEMENT} is not set`, () => { + setEnforcementConfig({}); + let consentData = { + vendorData: staticConfig.consentData.getTCData, + gdprApplies: true + } + gdprDataHandlerStub.returns(consentData); + const validate = sinon.stub().callsFake(() => false); + deviceAccessHook(nextFnSpy, true, 123, 'mockModule', undefined, {validate}); + sinon.assert.callCount(validate, 0); + sinon.assert.calledWith(nextFnSpy, true, 123, 'mockModule', {hasEnforcementHook: true, valid: true}); + }) }); describe('userSyncHook', function () { @@ -517,7 +504,7 @@ describe('gdpr enforcement', function () { const args = nextFnSpy.getCalls()[0].args; expect(args[1].hasValidated).to.be.true; expect(nextFnSpy.calledOnce).to.equal(true); - sinon.assert.calledWith(nextFnSpy, submodules, {...consentData, hasValidated: true}); + sinon.assert.calledWith(nextFnSpy, submodules, { ...consentData, hasValidated: true }); }); it('should allow userId module if gdpr not in scope', function () { @@ -571,7 +558,7 @@ describe('gdpr enforcement', function () { name: 'sampleUserId' } }] - sinon.assert.calledWith(nextFnSpy, expectedSubmodules, {...consentData, hasValidated: true}); + sinon.assert.calledWith(nextFnSpy, expectedSubmodules, { ...consentData, hasValidated: true }); }); }); @@ -630,17 +617,17 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); adapterManagerStub.withArgs('bidder_1').returns({ getSpec: function () { - return {'gvlid': 4} + return { 'gvlid': 4 } } }); adapterManagerStub.withArgs('bidder_2').returns({ getSpec: function () { - return {'gvlid': 5} + return { 'gvlid': 5 } } }); adapterManagerStub.withArgs('bidder_3').returns({ getSpec: function () { - return {'gvlid': undefined} + return { 'gvlid': undefined } } }); makeBidRequestsHook(nextFnSpy, MOCK_AD_UNITS, []); @@ -651,20 +638,20 @@ describe('gdpr enforcement', function () { code: 'ad-unit-1', mediaTypes: {}, bids: [ - sinon.match({bidder: 'bidder_1'}), - sinon.match({bidder: 'bidder_2'}) + sinon.match({ bidder: 'bidder_1' }), + sinon.match({ bidder: 'bidder_2' }) ] }, { code: 'ad-unit-2', mediaTypes: {}, bids: [ - sinon.match({bidder: 'bidder_2'}), - sinon.match({bidder: 'bidder_3'}) // should be allowed even though it's doesn't have a gvlId because liTransparency is established. + sinon.match({ bidder: 'bidder_2' }), + sinon.match({ bidder: 'bidder_3' }) // should be allowed even though it's doesn't have a gvlId because liTransparency is established. ] }], []); }); - it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is NOT established)', function () { + it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is NOT established)', function() { setEnforcementConfig({ gdpr: { rules: [{ @@ -688,17 +675,17 @@ describe('gdpr enforcement', function () { gdprDataHandlerStub.returns(consentData); adapterManagerStub.withArgs('bidder_1').returns({ getSpec: function () { - return {'gvlid': 4} + return { 'gvlid': 4 } } }); adapterManagerStub.withArgs('bidder_2').returns({ getSpec: function () { - return {'gvlid': 5} + return { 'gvlid': 5 } } }); adapterManagerStub.withArgs('bidder_3').returns({ getSpec: function () { - return {'gvlid': undefined} + return { 'gvlid': undefined } } }); @@ -710,13 +697,13 @@ describe('gdpr enforcement', function () { code: 'ad-unit-1', mediaTypes: {}, bids: [ - sinon.match({bidder: 'bidder_1'}), // 'bidder_2' is not present because it doesn't have vendorConsent + sinon.match({ bidder: 'bidder_1' }), // 'bidder_2' is not present because it doesn't have vendorConsent ] }, { code: 'ad-unit-2', mediaTypes: {}, bids: [ - sinon.match({bidder: 'bidder_3'}), // 'bidder_3' is allowed despite gvlId being undefined because it's part of vendorExceptions + sinon.match({ bidder: 'bidder_3' }), // 'bidder_3' is allowed despite gvlId being undefined because it's part of vendorExceptions ] }], []); @@ -774,12 +761,12 @@ describe('gdpr enforcement', function () { nextFnSpy = sandbox.spy(); }); - afterEach(function () { + afterEach(function() { config.resetConfig(); sandbox.restore(); }); - it('should block analytics adapter which does not have consent and allow the one(s) which have consent', function () { + it('should block analytics adapter which does not have consent and allow the one(s) which have consent', function() { setEnforcementConfig({ gdpr: { rules: [{ @@ -797,9 +784,9 @@ describe('gdpr enforcement', function () { consentData.gdprApplies = true; gdprDataHandlerStub.returns(consentData); - adapterManagerStub.withArgs('analyticsAdapter_A').returns({gvlid: 3}); - adapterManagerStub.withArgs('analyticsAdapter_B').returns({gvlid: 5}); - adapterManagerStub.withArgs('analyticsAdapter_C').returns({gvlid: 1}); + adapterManagerStub.withArgs('analyticsAdapter_A').returns({ gvlid: 3 }); + adapterManagerStub.withArgs('analyticsAdapter_B').returns({ gvlid: 5 }); + adapterManagerStub.withArgs('analyticsAdapter_C').returns({ gvlid: 1 }); enableAnalyticsHook(nextFnSpy, MOCK_ANALYTICS_ADAPTER_CONFIG); @@ -935,6 +922,33 @@ describe('gdpr enforcement', function () { expect(isAllowed).to.equal(true); }); + describe('when module does not need vendor consent', () => { + Object.entries({ + 'storage': 1, + 'basicAds': 2, + 'measurement': 7 + }).forEach(([purpose, purposeNo]) => { + describe(`for purpose ${purpose}`, () => { + const rule = createGdprRule(purpose); + Object.entries({ + 'allowed': true, + 'not allowed': false + }).forEach(([t, consentGiven]) => { + it(`should be ${t} when purpose is ${t}`, () => { + const consent = utils.deepClone(consentData); + consent.vendorData.purpose.consents[purposeNo] = consentGiven; + // vendor consent (and gvlid) should be ignored + consent.vendorData.vendor.consents[123] = !consentGiven; + // take legitimate interest out of the picture for this test + consent.vendorData.purpose.legitimateInterests = {}; + const actual = validateRules(rule, consent, 'mockModule', 123, () => true); + expect(actual).to.equal(consentGiven); + }) + }) + }) + }) + }) + describe('Purpose 2 special case', function () { const consentDataWithLIFalse = utils.deepClone(consentData); consentDataWithLIFalse.vendorData.purpose.legitimateInterests['2'] = false; @@ -1072,7 +1086,7 @@ describe('gdpr enforcement', function () { expect(purpose2Rule).to.deep.equal(purpose2RuleDefinedInConfig); }); - it('should use the "rules" defined in config if a definition found', function () { + it('should use the "rules" defined in config if a definition found', function() { const rules = [{ purpose: 'storage', enforcePurpose: false, @@ -1082,23 +1096,23 @@ describe('gdpr enforcement', function () { enforcePurpose: false, enforceVendor: false }] - setEnforcementConfig({gdpr: {rules}}); + setEnforcementConfig({gdpr: { rules }}); expect(enforcementRules).to.deep.equal(rules); }); }); - describe('TCF2FinalResults', function () { + describe('TCF2FinalResults', function() { let sandbox; - beforeEach(function () { + beforeEach(function() { sandbox = sinon.createSandbox(); sandbox.spy(events, 'emit'); }); - afterEach(function () { + afterEach(function() { config.resetConfig(); sandbox.restore(); }); - it('should emit TCF2 enforcement data on auction end', function () { + it('should emit TCF2 enforcement data on auction end', function() { const rules = [{ purpose: 'storage', enforcePurpose: false, @@ -1108,7 +1122,7 @@ describe('gdpr enforcement', function () { enforcePurpose: false, enforceVendor: false }] - setEnforcementConfig({gdpr: {rules}}); + setEnforcementConfig({gdpr: { rules }}); events.emit('auctionEnd', {}) @@ -1117,28 +1131,28 @@ describe('gdpr enforcement', function () { }) }); - describe('getGvlid', function () { + describe('getGvlid', function() { let sandbox; let getGvlidForBidAdapterStub; let getGvlidForUserIdModuleStub; let getGvlidForAnalyticsAdapterStub; - beforeEach(function () { + beforeEach(function() { sandbox = sinon.createSandbox(); getGvlidForBidAdapterStub = sandbox.stub(internal, 'getGvlidForBidAdapter'); getGvlidForUserIdModuleStub = sandbox.stub(internal, 'getGvlidForUserIdModule'); getGvlidForAnalyticsAdapterStub = sandbox.stub(internal, 'getGvlidForAnalyticsAdapter'); }); - afterEach(function () { + afterEach(function() { sandbox.restore(); config.resetConfig(); }); - it('should return "null" if called without passing any argument', function () { + it('should return "null" if called without passing any argument', function() { const gvlid = getGvlid(); expect(gvlid).to.equal(null); }); - it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function () { + it('should return "null" if GVL ID is not defined for any of these modules: Bid adapter, UserId submodule and Analytics adapter', function() { getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(null); @@ -1147,7 +1161,7 @@ describe('gdpr enforcement', function () { expect(gvlid).to.equal(null); }); - it('should return the GVL ID from gvlMapping if it is defined in setConfig', function () { + it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() { config.setConfig({ gvlMapping: { moduleA: 1 @@ -1161,7 +1175,7 @@ describe('gdpr enforcement', function () { expect(gvlid).to.equal(1); }); - it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function () { + it('should return the GVL ID by calling getGvlidForBidAdapter -> getGvlidForUserIdModule -> getGvlidForAnalyticsAdapter in sequence', function() { getGvlidForBidAdapterStub.withArgs('moduleA').returns(null); getGvlidForUserIdModuleStub.withArgs('moduleA').returns(null); getGvlidForAnalyticsAdapterStub.withArgs('moduleA').returns(7); diff --git a/test/spec/modules/glimpseBidAdapter_spec.js b/test/spec/modules/glimpseBidAdapter_spec.js index 7104493792f..353e61c7859 100644 --- a/test/spec/modules/glimpseBidAdapter_spec.js +++ b/test/spec/modules/glimpseBidAdapter_spec.js @@ -36,7 +36,7 @@ const mock = { refererInfo: { numIframes: 0, reachedTop: true, - referer: 'https://demo.glimpseprotocol.io/prebid/desktop', + page: 'https://demo.glimpseprotocol.io/prebid/desktop', stack: ['https://demo.glimpseprotocol.io/prebid/desktop'], }, }, @@ -167,7 +167,7 @@ describe('GlimpseProtocolAdapter', () => { it('Has referer information', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); - const expected = mock.bidderRequest.refererInfo.referer; + const expected = mock.bidderRequest.refererInfo.page; expect(payload.data.referer).to.equal(expected); }); @@ -273,16 +273,11 @@ describe('GlimpseProtocolAdapter', () => { }, }; - afterEach(() => { - config.getConfig.restore(); - }); - it('should keep all non-empty fields', () => { const fpdMock = fpdMockBase; - sinon.stub(config, 'getConfig').withArgs('ortb2').returns(fpdMock); const expected = fpdMockBase; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: fpdMock}); const payload = JSON.parse(request.data); const fpd = payload.data.fpd; @@ -293,7 +288,6 @@ describe('GlimpseProtocolAdapter', () => { const fpdMock = getDeepCopy(fpdMockBase); fpdMock.site.ext.data.fpdProvider.dataObject = {}; fpdMock.user.ext.data.fpdProvider = {}; - sinon.stub(config, 'getConfig').withArgs('ortb2').returns(fpdMock); const expected = { site: { @@ -312,7 +306,7 @@ describe('GlimpseProtocolAdapter', () => { }, }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: fpdMock}); const payload = JSON.parse(request.data); const fpd = payload.data.fpd; @@ -323,7 +317,6 @@ describe('GlimpseProtocolAdapter', () => { const fpdMock = getDeepCopy(fpdMockBase); fpdMock.site.ext.data.fpdProvider.dataArray = []; fpdMock.user.ext.data.fpdProvider.dataArray = []; - sinon.stub(config, 'getConfig').withArgs('ortb2').returns(fpdMock); const expected = { site: { @@ -356,7 +349,7 @@ describe('GlimpseProtocolAdapter', () => { }, }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: fpdMock}); const payload = JSON.parse(request.data); const fpd = payload.data.fpd; @@ -369,7 +362,6 @@ describe('GlimpseProtocolAdapter', () => { fpdMock.site.ext.data.fpdProvider.dataString = ''; fpdMock.user.keywords = ''; fpdMock.user.ext.data.fpdProvider.dataString = ''; - sinon.stub(config, 'getConfig').withArgs('ortb2').returns(fpdMock); const expected = { site: { @@ -400,7 +392,7 @@ describe('GlimpseProtocolAdapter', () => { }, }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: fpdMock}); const payload = JSON.parse(request.data); const fpd = payload.data.fpd; @@ -417,11 +409,10 @@ describe('GlimpseProtocolAdapter', () => { fpdMock.user.ext.data.fpdProvider.dataArray = []; fpdMock.user.ext.data.fpdProvider.dataObject = {}; fpdMock.user.ext.data.fpdProvider.dataString = ''; - sinon.stub(config, 'getConfig').withArgs('ortb2').returns(fpdMock); const expected = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2: fpdMock}); const payload = JSON.parse(request.data); const fpd = payload.data.fpd; diff --git a/test/spec/modules/glomexBidAdapter_spec.js b/test/spec/modules/glomexBidAdapter_spec.js index 6e5765c31f5..30157da858b 100644 --- a/test/spec/modules/glomexBidAdapter_spec.js +++ b/test/spec/modules/glomexBidAdapter_spec.js @@ -21,7 +21,7 @@ const BIDDER_REQUEST = { isAmp: true, numIframes: 0, reachedTop: true, - referer: 'https://glomex.com' + topmostLocation: 'https://glomex.com' }, gdprConsent: { gdprApplies: true, @@ -91,7 +91,12 @@ describe('glomexBidAdapter', function () { }) it('sends refererInfo', function () { - expect(request.data.refererInfo).to.eql(BIDDER_REQUEST.refererInfo) + const expected = { + ...BIDDER_REQUEST.refererInfo, + referer: BIDDER_REQUEST.refererInfo.topmostLocation + } + delete expected.topmostLocation; + expect(request.data.refererInfo).to.eql(expected) }) it('sends gdprConsent', function () { diff --git a/test/spec/modules/gmosspBidAdapter_spec.js b/test/spec/modules/gmosspBidAdapter_spec.js index 87c87600b97..6d290504194 100644 --- a/test/spec/modules/gmosspBidAdapter_spec.js +++ b/test/spec/modules/gmosspBidAdapter_spec.js @@ -62,7 +62,7 @@ describe('GmosspAdapter', function () { it('sends bid request to ENDPOINT via GET', function () { const bidderRequest = { refererInfo: { - referer: 'https://hoge.com' + topmostLocation: 'https://hoge.com' } }; const requests = spec.buildRequests(bidRequests, bidderRequest); @@ -74,7 +74,7 @@ describe('GmosspAdapter', function () { it('should use fallback if refererInfo.referer in bid request is empty and im_uid ,shared_id, idl_env cookie is empty', function () { const bidderRequest = { refererInfo: { - referer: '' + topmostLocation: '' }, }; bidRequests[0].userId.imuid = ''; diff --git a/test/spec/modules/gnetBidAdapter_spec.js b/test/spec/modules/gnetBidAdapter_spec.js index a69b196bc5c..21526aba201 100644 --- a/test/spec/modules/gnetBidAdapter_spec.js +++ b/test/spec/modules/gnetBidAdapter_spec.js @@ -69,7 +69,7 @@ describe('gnetAdapter', function () { const bidderRequest = { refererInfo: { - referer: 'https://gnetrtb.com' + page: 'https://gnetrtb.com' } }; diff --git a/test/spec/modules/goldbachBidAdapter_spec.js b/test/spec/modules/goldbachBidAdapter_spec.js index 459cda7958f..fc2c1beb900 100644 --- a/test/spec/modules/goldbachBidAdapter_spec.js +++ b/test/spec/modules/goldbachBidAdapter_spec.js @@ -802,7 +802,7 @@ describe('GoldbachXandrAdapter', function () { const bidRequest = Object.assign({}, bidRequests[0]) const bidderRequest = { refererInfo: { - referer: 'https://example.com/page.html', + topmostLocation: 'https://example.com/page.html', reachedTop: true, numIframes: 2, stack: [ @@ -919,11 +919,7 @@ describe('GoldbachXandrAdapter', function () { uid2: { id: 'sample-uid2-value' }, criteoId: 'sample-criteo-userid', netId: 'sample-netId-userid', - idl_env: 'sample-idl-userid', - flocId: { - id: 'sample-flocid-value', - version: 'chrome.1.0' - } + idl_env: 'sample-idl-userid' } }); @@ -940,11 +936,6 @@ describe('GoldbachXandrAdapter', function () { id: 'sample-criteo-userid', }); - expect(payload.eids).to.deep.include({ - source: 'chrome.com', - id: 'sample-flocid-value' - }); - expect(payload.eids).to.deep.include({ source: 'netid.de', id: 'sample-netId-userid', diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 61776f58682..94524e07fb3 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -44,12 +44,12 @@ describe('TheMediaGrid Adapter', function () { return JSON.parse(data); } const bidderRequest = { - refererInfo: {referer: 'https://example.com'}, + refererInfo: {page: 'https://example.com'}, bidderRequestId: '22edbae2733bf6', auctionId: '9e2dfbfe-00c7-4f5e-9850-4044df3229c7', timeout: 3000 }; - const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + const referrer = encodeURIComponent(bidderRequest.refererInfo.page); let bidRequests = [ { 'bidder': 'grid', @@ -126,14 +126,10 @@ describe('TheMediaGrid Adapter', function () { genre: 'Adventure' } }; - - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'ortb2.site' ? site : null); - const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const request = spec.buildRequests([bidRequests[0]], {...bidderRequest, ortb2: {site}}); const payload = parseRequest(request.data); expect(payload.site.cat).to.deep.equal([...site.cat, ...site.pagecat]); expect(payload.site.content.genre).to.deep.equal(site.content.genre); - getConfigStub.restore(); }); it('should attach valid params to the tag', function () { @@ -405,16 +401,14 @@ describe('TheMediaGrid Adapter', function () { screenHeight: 800, language: 'ru' }; - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'ortb2.user.ext.device' ? ortb2UserExtDevice : null); + const ortb2 = {user: {ext: {device: ortb2UserExtDevice}}}; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, {...bidderRequest, ortb2}); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload).to.have.property('user'); expect(payload.user).to.have.property('ext'); expect(payload.user.ext.device).to.deep.equal(ortb2UserExtDevice); - getConfigStub.restore(); }); it('if schain is present payload must have source.ext.schain param', function () { @@ -473,8 +467,10 @@ describe('TheMediaGrid Adapter', function () { }); it('should contain the keyword values if it present in ortb2.(site/user)', function () { - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'ortb2.user' ? {'keywords': 'foo,any'} : (arg === 'ortb2.site' ? {'keywords': 'bar'} : null)); + const ortb2 = { + user: {'keywords': 'foo,any'}, + site: {'keywords': 'bar'} + }; const keywords = { 'site': { 'somePublisher': [ @@ -498,7 +494,7 @@ describe('TheMediaGrid Adapter', function () { } }; const bidRequestWithKW = { ...bidRequests[0], params: { ...bidRequests[0].params, keywords } } - const request = spec.buildRequests([bidRequestWithKW], bidderRequest); + const request = spec.buildRequests([bidRequestWithKW], {...bidderRequest, ortb2}); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); expect(payload.ext.keywords).to.deep.equal({ @@ -543,7 +539,6 @@ describe('TheMediaGrid Adapter', function () { ] } }); - getConfigStub.restore(); }); it('should have user.data filled from config ortb2.user.data', function () { @@ -560,13 +555,10 @@ describe('TheMediaGrid Adapter', function () { someKey: 'another data' } ]; - - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'ortb2.user.data' ? userData : null); - const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const ortb2 = {user: {data: userData}}; + const request = spec.buildRequests([bidRequests[0]], {...bidderRequest, ortb2}); const payload = parseRequest(request.data); expect(payload.user.data).to.deep.equal(userData); - getConfigStub.restore(); }); it('should have site.content.data filled from config ortb2.site.content.data', function () { @@ -582,13 +574,10 @@ describe('TheMediaGrid Adapter', function () { ] } ]; - - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'ortb2.site' ? { content: { data: contentData } } : null); - const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const ortb2 = {site: { content: { data: contentData } }}; + const request = spec.buildRequests([bidRequests[0]], {...bidderRequest, ortb2}); const payload = parseRequest(request.data); expect(payload.site.content.data).to.deep.equal(contentData); - getConfigStub.restore(); }); it('should have right value in user.data when jwpsegments are present', function () { @@ -605,8 +594,7 @@ describe('TheMediaGrid Adapter', function () { someKey: 'another data' } ]; - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'ortb2.user.data' ? userData : null); + const ortb2 = {user: {data: userData}}; const jsContent = {id: 'test_jw_content_id'}; const jsSegments = ['test_seg_1', 'test_seg_2']; @@ -620,7 +608,7 @@ describe('TheMediaGrid Adapter', function () { } } }); - const request = spec.buildRequests([bidRequestsWithJwTargeting], bidderRequest); + const request = spec.buildRequests([bidRequestsWithJwTargeting], {...bidderRequest, ortb2}); const payload = parseRequest(request.data); expect(payload.user.data).to.deep.equal([{ name: 'iow_labs_pub_data', @@ -629,18 +617,14 @@ describe('TheMediaGrid Adapter', function () { {name: 'jwpseg', value: jsSegments[1]} ] }, ...userData]); - getConfigStub.restore(); }); it('should have site.content.id filled from config ortb2.site.content.id', function () { const contentId = 'jw_abc'; - - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'ortb2.site' ? { content: { id: contentId } } : null); - const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const ortb2 = {site: {content: {id: contentId}}}; + const request = spec.buildRequests([bidRequests[0]], {...bidderRequest, ortb2}); const payload = parseRequest(request.data); expect(payload.site.content.id).to.equal(contentId); - getConfigStub.restore(); }); it('should be right tmax when timeout in config is less then timeout in bidderRequest', function() { @@ -760,12 +744,12 @@ describe('TheMediaGrid Adapter', function () { 'auctionId': 654645, }; const bidderRequestWithNumId = { - refererInfo: {referer: 'https://example.com'}, + refererInfo: {page: 'https://example.com'}, bidderRequestId: 345345345, auctionId: 654645, timeout: 3000 }; - const parsedReferrer = encodeURIComponent(bidderRequestWithNumId.refererInfo.referer); + const parsedReferrer = encodeURIComponent(bidderRequestWithNumId.refererInfo.page); const request = spec.buildRequests([bidRequestWithNumId], bidderRequestWithNumId); expect(request.data).to.be.an('string'); const payload = parseRequest(request.data); diff --git a/test/spec/modules/gridNMBidAdapter_spec.js b/test/spec/modules/gridNMBidAdapter_spec.js index 89efe942c1f..b400ef3394d 100644 --- a/test/spec/modules/gridNMBidAdapter_spec.js +++ b/test/spec/modules/gridNMBidAdapter_spec.js @@ -184,9 +184,9 @@ describe('TheMediaGridNM Adapter', function () { bidderRequestId: '22edbae2733bf6', auctionId: '1d1a030790a475', timeout: 3000, - refererInfo: { referer: 'https://example.com' } + refererInfo: { page: 'https://example.com' } }; - const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + const referrer = encodeURIComponent(bidderRequest.refererInfo.page); let bidRequests = [ { 'bidder': 'gridNM', @@ -226,6 +226,38 @@ describe('TheMediaGridNM Adapter', function () { } ]; + it('if content and segment is present in jwTargeting, payload must have right params', function () { + const jsContent = {id: 'test_jw_content_id'}; + const jsSegments = ['test_seg_1', 'test_seg_2']; + const bidRequestsWithJwTargeting = bidRequests.map((bid) => { + return Object.assign({ + rtd: { + jwplayer: { + targeting: { + segments: jsSegments, + content: jsContent + } + } + } + }, bid); + }); + const requests = spec.buildRequests(bidRequestsWithJwTargeting, bidderRequest); + requests.forEach((req, i) => { + const payload = req.data; + expect(req).to.have.property('data'); + expect(payload).to.have.property('user'); + expect(payload.user.data).to.deep.equal([{ + name: 'iow_labs_pub_data', + segment: [ + {name: 'jwpseg', value: jsSegments[0]}, + {name: 'jwpseg', value: jsSegments[1]} + ] + }]); + expect(payload).to.have.property('site'); + expect(payload.site.content).to.deep.equal(jsContent); + }); + }); + it('should attach valid params to the tag', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); const requestsSizes = ['300x250,300x600', '728x90']; diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index ebbc1c230f1..17fff31f132 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -279,6 +279,11 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.pi).to.equal(3); }); + it('should set the correct pi param if product param is found and is equal to skins', function () { + const request = { ...bidRequests[0], params: { ...zoneParam, product: 'Skins' } }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pi).to.equal(8); + }); it('should default the pi param to 2 if only zone or pubId param is found', function () { const zoneRequest = { ...bidRequests[0], params: zoneParam }; const pubIdRequest = { ...bidRequests[0], params: pubIdParam }; @@ -546,7 +551,7 @@ describe('gumgumAdapter', function () { }); it('should handle no gg params', function () { - const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { referer: 'https://www.prebid.org/?param1=foo¶m2=bar¶m3=baz' } })[0]; + const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { page: 'https://www.prebid.org/?param1=foo¶m2=bar¶m3=baz' } })[0]; // no params are in object expect(bidRequest.data.hasOwnProperty('eAdBuyId')).to.be.false; @@ -555,7 +560,7 @@ describe('gumgumAdapter', function () { }); it('should handle encrypted ad buy id', function () { - const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { referer: 'https://www.prebid.org/?param1=foo&ggad=bar¶m3=baz' } })[0]; + const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { page: 'https://www.prebid.org/?param1=foo&ggad=bar¶m3=baz' } })[0]; // correct params are in object expect(bidRequest.data.hasOwnProperty('eAdBuyId')).to.be.true; @@ -567,7 +572,7 @@ describe('gumgumAdapter', function () { }); it('should handle unencrypted ad buy id', function () { - const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { referer: 'https://www.prebid.org/?param1=foo&ggad=123¶m3=baz' } })[0]; + const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { page: 'https://www.prebid.org/?param1=foo&ggad=123¶m3=baz' } })[0]; // correct params are in object expect(bidRequest.data.hasOwnProperty('eAdBuyId')).to.be.false; @@ -579,7 +584,7 @@ describe('gumgumAdapter', function () { }); it('should handle multiple gg params', function () { - const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { referer: 'https://www.prebid.org/?ggdeal=foo&ggad=bar¶m3=baz' } })[0]; + const bidRequest = spec.buildRequests(bidRequests, { refererInfo: { page: 'https://www.prebid.org/?ggdeal=foo&ggad=bar¶m3=baz' } })[0]; // correct params are in object expect(bidRequest.data.hasOwnProperty('eAdBuyId')).to.be.true; diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index 77eeb70326b..ca9eadc7fd4 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -22,7 +22,7 @@ describe('HadronIdSystem', function () { const callback = hadronIdSubmodule.getId(config).callback; callback(callbackSpy); const request = server.requests[0]; - expect(request.url).to.eq(`https://id.hadron.ad.gt/api/v1/pbhid`); + expect(request.url).to.eq(`https://id.hadron.ad.gt/api/v1/pbhid?partner_id=0&_it=prebid`); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); }); @@ -49,7 +49,7 @@ describe('HadronIdSystem', function () { const callback = hadronIdSubmodule.getId(config).callback; callback(callbackSpy); const request = server.requests[0]; - expect(request.url).to.eq('https://hadronid.publync.com'); + expect(request.url).to.eq('https://hadronid.publync.com?partner_id=0&_it=prebid'); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); expect(callbackSpy.lastCall.lastArg).to.deep.equal({hadronId: 'testHadronId1'}); }); diff --git a/test/spec/modules/hadronRtdProvider_spec.js b/test/spec/modules/hadronRtdProvider_spec.js index 30e4947566f..b9e07c97f84 100644 --- a/test/spec/modules/hadronRtdProvider_spec.js +++ b/test/spec/modules/hadronRtdProvider_spec.js @@ -1,3 +1,5 @@ +// TODO: this and hadronRtdProvider_spec are a copy-paste of each other + import {config} from 'src/config.js'; import {HALOID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, hadronSubmodule, storage} from 'modules/hadronRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; @@ -25,7 +27,6 @@ describe('hadronRtdProvider', function() { describe('Add Real-Time Data', function() { it('merges ortb2 data', function() { let rtdConfig = {}; - let bidConfig = {}; const setConfigUserObj1 = { name: 'www.dataprovider1.com', @@ -58,18 +59,20 @@ describe('hadronRtdProvider', function() { ] } - config.setConfig({ - ortb2: { - user: { - data: [setConfigUserObj1, setConfigUserObj2] - }, - site: { - content: { - data: [setConfigSiteObj1] + let bidConfig = { + ortb2Fragments: { + global: { + user: { + data: [setConfigUserObj1, setConfigUserObj2] + }, + site: { + content: { + data: [setConfigSiteObj1] + } } } } - }); + }; const rtdUserObj1 = { name: 'www.dataprovider4.com', @@ -116,7 +119,7 @@ describe('hadronRtdProvider', function() { addRealTimeData(bidConfig, rtd, rtdConfig); - let ortb2Config = config.getConfig().ortb2; + let ortb2Config = bidConfig.ortb2Fragments.global; expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1, setConfigUserObj2, rtdUserObj1]); expect(ortb2Config.site.content.data).to.deep.include.members([setConfigSiteObj1, rtdSiteObj1]); @@ -124,7 +127,6 @@ describe('hadronRtdProvider', function() { it('merges ortb2 data without duplication', function() { let rtdConfig = {}; - let bidConfig = {}; const userObj1 = { name: 'www.dataprovider1.com', @@ -157,18 +159,20 @@ describe('hadronRtdProvider', function() { ] } - config.setConfig({ - ortb2: { - user: { - data: [userObj1, userObj2] - }, - site: { - content: { - data: [siteObj1] + let bidConfig = { + ortb2Fragments: { + global: { + user: { + data: [userObj1, userObj2] + }, + site: { + content: { + data: [siteObj1] + } } } } - }); + }; const rtd = { ortb2: { @@ -185,7 +189,7 @@ describe('hadronRtdProvider', function() { addRealTimeData(bidConfig, rtd, rtdConfig); - let ortb2Config = config.getConfig().ortb2; + let ortb2Config = bidConfig.ortb2Fragments.global; expect(ortb2Config.user.data).to.deep.include.members([userObj1, userObj2]); expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1]); @@ -195,7 +199,6 @@ describe('hadronRtdProvider', function() { it('merges bidder-specific ortb2 data', function() { let rtdConfig = {}; - let bidConfig = {}; const configUserObj1 = { name: 'www.dataprovider1.com', @@ -248,37 +251,32 @@ describe('hadronRtdProvider', function() { ] }; - config.setBidderConfig({ - bidders: ['adbuzz'], - config: { - ortb2: { - user: { - data: [configUserObj1, configUserObj2] - }, - site: { - content: { - data: [configSiteObj1] + let bidConfig = { + ortb2Fragments: { + bidder: { + adbuzz: { + user: { + data: [configUserObj1, configUserObj2] + }, + site: { + content: { + data: [configSiteObj1] + } } - } - } - } - }); - - config.setBidderConfig({ - bidders: ['pubvisage'], - config: { - ortb2: { - user: { - data: [configUserObj3] }, - site: { - content: { - data: [configSiteObj2] + pubvisage: { + user: { + data: [configUserObj3] + }, + site: { + content: { + data: [configSiteObj2] + } } } } } - }); + }; const rtdUserObj1 = { name: 'www.dataprovider4.com', @@ -365,12 +363,12 @@ describe('hadronRtdProvider', function() { addRealTimeData(bidConfig, rtd, rtdConfig); - let ortb2Config = config.getBidderConfig().adbuzz.ortb2; + let ortb2Config = bidConfig.ortb2Fragments.bidder.adbuzz; expect(ortb2Config.user.data).to.deep.include.members([configUserObj1, configUserObj2, rtdUserObj1]); expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj1, rtdSiteObj1]); - ortb2Config = config.getBidderConfig().pubvisage.ortb2; + ortb2Config = bidConfig.ortb2Fragments.bidder.pubvisage; expect(ortb2Config.user.data).to.deep.include.members([configUserObj3, rtdUserObj2]); expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj2, rtdSiteObj2]); @@ -378,7 +376,6 @@ describe('hadronRtdProvider', function() { it('merges bidder-specific ortb2 data without duplication', function() { let rtdConfig = {}; - let bidConfig = {}; const userObj1 = { name: 'www.dataprovider1.com', @@ -431,37 +428,32 @@ describe('hadronRtdProvider', function() { ] }; - config.setBidderConfig({ - bidders: ['adbuzz'], - config: { - ortb2: { - user: { - data: [userObj1, userObj2] - }, - site: { - content: { - data: [siteObj1] + let bidConfig = { + ortb2Fragments: { + bidder: { + adbuzz: { + user: { + data: [userObj1, userObj2] + }, + site: { + content: { + data: [siteObj1] + } } - } - } - } - }); - - config.setBidderConfig({ - bidders: ['pubvisage'], - config: { - ortb2: { - user: { - data: [userObj3] }, - site: { - content: { - data: [siteObj2] + pubvisage: { + user: { + data: [userObj3] + }, + site: { + content: { + data: [siteObj2] + } } } } } - }); + }; const rtd = { ortb2b: { @@ -494,7 +486,7 @@ describe('hadronRtdProvider', function() { addRealTimeData(bidConfig, rtd, rtdConfig); - let ortb2Config = config.getBidderConfig().adbuzz.ortb2; + let ortb2Config = bidConfig.ortb2Fragments.bidder.adbuzz; expect(ortb2Config.user.data).to.deep.include.members([userObj1]); expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1]); @@ -502,7 +494,7 @@ describe('hadronRtdProvider', function() { expect(ortb2Config.user.data).to.have.lengthOf(2); expect(ortb2Config.site.content.data).to.have.lengthOf(1); - ortb2Config = config.getBidderConfig().pubvisage.ortb2; + ortb2Config = bidConfig.ortb2Fragments.bidder.pubvisage; expect(ortb2Config.user.data).to.deep.include.members([userObj3, userObj3]); expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1, siteObj2]); @@ -647,7 +639,7 @@ describe('hadronRtdProvider', function() { } }; - const bidConfig = {}; + const bidConfig = {ortb2Fragments: {global: {}}}; const rtdUserObj1 = { name: 'www.dataprovider3.com', @@ -676,9 +668,8 @@ describe('hadronRtdProvider', function() { getDataFromLocalStorageStub.withArgs(RTD_LOCAL_NAME).returns(JSON.stringify(cachedRtd)); - expect(config.getConfig().ortb2).to.be.undefined; getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); }); it('gets real-time data via async request', function() { @@ -698,16 +689,6 @@ describe('hadronRtdProvider', function() { ] } - config.setConfig({ - ortb2: { - site: { - content: { - data: [setConfigSiteObj1] - } - } - } - }); - const rtdConfig = { params: { segmentCache: false, @@ -718,7 +699,17 @@ describe('hadronRtdProvider', function() { } }; - let bidConfig = {}; + let bidConfig = { + ortb2Fragments: { + global: { + site: { + content: { + data: [setConfigSiteObj1] + } + } + } + } + }; const rtdUserObj1 = { name: 'www.audigent.com', @@ -756,7 +747,7 @@ describe('hadronRtdProvider', function() { request.respond(200, responseHeader, JSON.stringify(data)); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); }); }); }); diff --git a/test/spec/modules/haloIdSystem_spec.js b/test/spec/modules/haloIdSystem_spec.js deleted file mode 100644 index 0b8fff12abe..00000000000 --- a/test/spec/modules/haloIdSystem_spec.js +++ /dev/null @@ -1,57 +0,0 @@ -import { haloIdSubmodule, storage } from 'modules/haloIdSystem.js'; -import { server } from 'test/mocks/xhr.js'; -import * as utils from 'src/utils.js'; - -describe('HaloIdSystem', function () { - describe('getId', function() { - let getDataFromLocalStorageStub; - - beforeEach(function() { - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); - }); - - afterEach(function () { - getDataFromLocalStorageStub.restore(); - }); - - it('gets a haloId', function() { - const config = { - params: {} - }; - const callbackSpy = sinon.spy(); - const callback = haloIdSubmodule.getId(config).callback; - callback(callbackSpy); - const request = server.requests[0]; - expect(request.url).to.eq(`https://id.halo.ad.gt/api/v1/pbhid`); - request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ haloId: 'testHaloId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({haloId: 'testHaloId1'}); - }); - - it('gets a cached haloid', function() { - const config = { - params: {} - }; - getDataFromLocalStorageStub.withArgs('auHaloId').returns('tstCachedHaloId1'); - - const callbackSpy = sinon.spy(); - const callback = haloIdSubmodule.getId(config).callback; - callback(callbackSpy); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({haloId: 'tstCachedHaloId1'}); - }); - - it('allows configurable id url', function() { - const config = { - params: { - url: 'https://haloid.publync.com' - } - }; - const callbackSpy = sinon.spy(); - const callback = haloIdSubmodule.getId(config).callback; - callback(callbackSpy); - const request = server.requests[0]; - expect(request.url).to.eq('https://haloid.publync.com'); - request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ haloId: 'testHaloId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({haloId: 'testHaloId1'}); - }); - }); -}); diff --git a/test/spec/modules/haloRtdProvider_spec.js b/test/spec/modules/haloRtdProvider_spec.js deleted file mode 100644 index 32c0338b87f..00000000000 --- a/test/spec/modules/haloRtdProvider_spec.js +++ /dev/null @@ -1,762 +0,0 @@ -import {config} from 'src/config.js'; -import {HALOID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, haloSubmodule, storage} from 'modules/haloRtdProvider.js'; -import {server} from 'test/mocks/xhr.js'; - -const responseHeader = {'Content-Type': 'application/json'}; - -describe('haloRtdProvider', function() { - let getDataFromLocalStorageStub; - - beforeEach(function() { - config.resetConfig(); - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); - }); - - afterEach(function () { - getDataFromLocalStorageStub.restore(); - }); - - describe('haloSubmodule', function() { - it('successfully instantiates', function () { - expect(haloSubmodule.init()).to.equal(true); - }); - }); - - describe('Add Real-Time Data', function() { - it('merges ortb2 data', function() { - let rtdConfig = {}; - let bidConfig = {}; - - const setConfigUserObj1 = { - name: 'www.dataprovider1.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, - segment: [{ - id: '1776' - }] - }; - - const setConfigUserObj2 = { - name: 'www.dataprovider2.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, - segment: [{ - id: '1914' - }] - }; - - const setConfigSiteObj1 = { - name: 'www.dataprovider3.com', - ext: { - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [ - { - id: '1812' - }, - { - id: '1955' - } - ] - } - - config.setConfig({ - ortb2: { - user: { - data: [setConfigUserObj1, setConfigUserObj2] - }, - site: { - content: { - data: [setConfigSiteObj1] - } - } - } - }); - - const rtdUserObj1 = { - name: 'www.dataprovider4.com', - ext: { - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [ - { - id: '1918' - }, - { - id: '1939' - } - ] - }; - - const rtdSiteObj1 = { - name: 'www.dataprovider5.com', - ext: { - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [ - { - id: '1945' - }, - { - id: '2003' - } - ] - }; - - const rtd = { - ortb2: { - user: { - data: [rtdUserObj1] - }, - site: { - content: { - data: [rtdSiteObj1] - } - } - } - }; - - addRealTimeData(bidConfig, rtd, rtdConfig); - - let ortb2Config = config.getConfig().ortb2; - - expect(ortb2Config.user.data).to.deep.include.members([setConfigUserObj1, setConfigUserObj2, rtdUserObj1]); - expect(ortb2Config.site.content.data).to.deep.include.members([setConfigSiteObj1, rtdSiteObj1]); - }); - - it('merges ortb2 data without duplication', function() { - let rtdConfig = {}; - let bidConfig = {}; - - const userObj1 = { - name: 'www.dataprovider1.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, - segment: [{ - id: '1776' - }] - }; - - const userObj2 = { - name: 'www.dataprovider2.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, - segment: [{ - id: '1914' - }] - }; - - const siteObj1 = { - name: 'www.dataprovider3.com', - ext: { - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [ - { - id: '1812' - }, - { - id: '1955' - } - ] - } - - config.setConfig({ - ortb2: { - user: { - data: [userObj1, userObj2] - }, - site: { - content: { - data: [siteObj1] - } - } - } - }); - - const rtd = { - ortb2: { - user: { - data: [userObj1] - }, - site: { - content: { - data: [siteObj1] - } - } - } - }; - - addRealTimeData(bidConfig, rtd, rtdConfig); - - let ortb2Config = config.getConfig().ortb2; - - expect(ortb2Config.user.data).to.deep.include.members([userObj1, userObj2]); - expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1]); - expect(ortb2Config.user.data).to.have.lengthOf(2); - expect(ortb2Config.site.content.data).to.have.lengthOf(1); - }); - - it('merges bidder-specific ortb2 data', function() { - let rtdConfig = {}; - let bidConfig = {}; - - const configUserObj1 = { - name: 'www.dataprovider1.com', - ext: { segtax: 3 }, - segment: [{ - id: '1776' - }] - }; - - const configUserObj2 = { - name: 'www.dataprovider2.com', - ext: { segtax: 3 }, - segment: [{ - id: '1914' - }] - }; - - const configUserObj3 = { - name: 'www.dataprovider1.com', - ext: { segtax: 3 }, - segment: [{ - id: '2003' - }] - }; - - const configSiteObj1 = { - name: 'www.dataprovider3.com', - ext: { - segtax: 1 - }, - segment: [ - { - id: '1812' - }, - { - id: '1955' - } - ] - }; - - const configSiteObj2 = { - name: 'www.dataprovider3.com', - ext: { - segtax: 1 - }, - segment: [ - { - id: '1812' - } - ] - }; - - config.setBidderConfig({ - bidders: ['adbuzz'], - config: { - ortb2: { - user: { - data: [configUserObj1, configUserObj2] - }, - site: { - content: { - data: [configSiteObj1] - } - } - } - } - }); - - config.setBidderConfig({ - bidders: ['pubvisage'], - config: { - ortb2: { - user: { - data: [configUserObj3] - }, - site: { - content: { - data: [configSiteObj2] - } - } - } - } - }); - - const rtdUserObj1 = { - name: 'www.dataprovider4.com', - ext: { - segtax: 501 - }, - segment: [ - { - id: '1918' - }, - { - id: '1939' - } - ] - }; - - const rtdUserObj2 = { - name: 'www.dataprovider2.com', - ext: { - segtax: 502 - }, - segment: [ - { - id: '1939' - } - ] - }; - - const rtdSiteObj1 = { - name: 'www.dataprovider5.com', - ext: { - segtax: 1 - }, - segment: [ - { - id: '441' - }, - { - id: '442' - } - ] - }; - - const rtdSiteObj2 = { - name: 'www.dataprovider6.com', - ext: { - segtax: 2 - }, - segment: [ - { - id: '676' - } - ] - }; - - const rtd = { - ortb2b: { - adbuzz: { - ortb2: { - user: { - data: [rtdUserObj1] - }, - site: { - content: { - data: [rtdSiteObj1] - } - } - } - }, - pubvisage: { - ortb2: { - user: { - data: [rtdUserObj2] - }, - site: { - content: { - data: [rtdSiteObj2] - } - } - } - } - } - }; - - addRealTimeData(bidConfig, rtd, rtdConfig); - - let ortb2Config = config.getBidderConfig().adbuzz.ortb2; - - expect(ortb2Config.user.data).to.deep.include.members([configUserObj1, configUserObj2, rtdUserObj1]); - expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj1, rtdSiteObj1]); - - ortb2Config = config.getBidderConfig().pubvisage.ortb2; - - expect(ortb2Config.user.data).to.deep.include.members([configUserObj3, rtdUserObj2]); - expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj2, rtdSiteObj2]); - }); - - it('merges bidder-specific ortb2 data without duplication', function() { - let rtdConfig = {}; - let bidConfig = {}; - - const userObj1 = { - name: 'www.dataprovider1.com', - ext: { segtax: 3 }, - segment: [{ - id: '1776' - }] - }; - - const userObj2 = { - name: 'www.dataprovider2.com', - ext: { segtax: 3 }, - segment: [{ - id: '1914' - }] - }; - - const userObj3 = { - name: 'www.dataprovider1.com', - ext: { segtax: 3 }, - segment: [{ - id: '2003' - }] - }; - - const siteObj1 = { - name: 'www.dataprovider3.com', - ext: { - segtax: 1 - }, - segment: [ - { - id: '1812' - }, - { - id: '1955' - } - ] - }; - - const siteObj2 = { - name: 'www.dataprovider3.com', - ext: { - segtax: 1 - }, - segment: [ - { - id: '1812' - } - ] - }; - - config.setBidderConfig({ - bidders: ['adbuzz'], - config: { - ortb2: { - user: { - data: [userObj1, userObj2] - }, - site: { - content: { - data: [siteObj1] - } - } - } - } - }); - - config.setBidderConfig({ - bidders: ['pubvisage'], - config: { - ortb2: { - user: { - data: [userObj3] - }, - site: { - content: { - data: [siteObj2] - } - } - } - } - }); - - const rtd = { - ortb2b: { - adbuzz: { - ortb2: { - user: { - data: [userObj1] - }, - site: { - content: { - data: [siteObj1] - } - } - } - }, - pubvisage: { - ortb2: { - user: { - data: [userObj2, userObj3] - }, - site: { - content: { - data: [siteObj1, siteObj2] - } - } - } - } - } - }; - - addRealTimeData(bidConfig, rtd, rtdConfig); - - let ortb2Config = config.getBidderConfig().adbuzz.ortb2; - - expect(ortb2Config.user.data).to.deep.include.members([userObj1]); - expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1]); - - expect(ortb2Config.user.data).to.have.lengthOf(2); - expect(ortb2Config.site.content.data).to.have.lengthOf(1); - - ortb2Config = config.getBidderConfig().pubvisage.ortb2; - - expect(ortb2Config.user.data).to.deep.include.members([userObj3, userObj3]); - expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1, siteObj2]); - - expect(ortb2Config.user.data).to.have.lengthOf(2); - expect(ortb2Config.site.content.data).to.have.lengthOf(2); - }); - - it('allows publisher defined rtd ortb2 logic', function() { - const rtdConfig = { - params: { - handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { - if (rtd.ortb2.user.data[0].segment[0].id == '1776') { - pbConfig.setConfig({ortb2: rtd.ortb2}); - } else { - pbConfig.setConfig({ortb2: {}}); - } - } - } - }; - - let bidConfig = {}; - - const rtdUserObj1 = { - name: 'www.dataprovider.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, - segment: [{ - id: '1776' - }] - }; - - let rtd = { - ortb2: { - user: { - data: [rtdUserObj1] - } - } - }; - - config.resetConfig(); - - let pbConfig = config.getConfig(); - addRealTimeData(bidConfig, rtd, rtdConfig); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); - - const rtdUserObj2 = { - name: 'www.audigent.com', - ext: { - segtax: '1', - taxprovider: '1' - }, - segment: [{ - id: 'pubseg1' - }] - }; - - rtd = { - ortb2: { - user: { - data: [rtdUserObj2] - } - } - }; - - config.resetConfig(); - - pbConfig = config.getConfig(); - addRealTimeData(bidConfig, rtd, rtdConfig); - expect(config.getConfig().ortb2).to.deep.equal({}); - }); - - it('allows publisher defined adunit logic', function() { - const rtdConfig = { - params: { - handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { - var adUnits = bidConfig.adUnits; - for (var i = 0; i < adUnits.length; i++) { - var adUnit = adUnits[i]; - for (var j = 0; j < adUnit.bids.length; j++) { - var bid = adUnit.bids[j]; - if (bid.bidder == 'adBuzz') { - for (var k = 0; k < rtd.adBuzz.length; k++) { - bid.adBuzzData.segments.adBuzz.push(rtd.adBuzz[k]); - } - } else if (bid.bidder == 'trueBid') { - for (var k = 0; k < rtd.trueBid.length; k++) { - bid.trueBidSegments.push(rtd.trueBid[k]); - } - } - } - } - } - } - }; - - let bidConfig = { - adUnits: [ - { - bids: [ - { - bidder: 'adBuzz', - adBuzzData: { - segments: { - adBuzz: [ - { - id: 'adBuzzSeg1' - } - ] - } - } - }, - { - bidder: 'trueBid', - trueBidSegments: [] - } - ] - } - ] - }; - - const rtd = { - adBuzz: [{id: 'adBuzzSeg2'}, {id: 'adBuzzSeg3'}], - trueBid: [{id: 'truebidSeg1'}, {id: 'truebidSeg2'}, {id: 'truebidSeg3'}] - }; - - addRealTimeData(bidConfig, rtd, rtdConfig); - - expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[0].id).to.equal('adBuzzSeg1'); - expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[1].id).to.equal('adBuzzSeg2'); - expect(bidConfig.adUnits[0].bids[0].adBuzzData.segments.adBuzz[2].id).to.equal('adBuzzSeg3'); - expect(bidConfig.adUnits[0].bids[1].trueBidSegments[0].id).to.equal('truebidSeg1'); - expect(bidConfig.adUnits[0].bids[1].trueBidSegments[1].id).to.equal('truebidSeg2'); - expect(bidConfig.adUnits[0].bids[1].trueBidSegments[2].id).to.equal('truebidSeg3'); - }); - }); - - describe('Get Real-Time Data', function() { - it('gets rtd from local storage cache', function() { - const rtdConfig = { - params: { - segmentCache: true - } - }; - - const bidConfig = {}; - - const rtdUserObj1 = { - name: 'www.dataprovider3.com', - ext: { - taxonomyname: 'iab_audience_taxonomy' - }, - segment: [ - { - id: '1918' - }, - { - id: '1939' - } - ] - }; - - const cachedRtd = { - rtd: { - ortb2: { - user: { - data: [rtdUserObj1] - } - } - } - }; - - getDataFromLocalStorageStub.withArgs(RTD_LOCAL_NAME).returns(JSON.stringify(cachedRtd)); - - expect(config.getConfig().ortb2).to.be.undefined; - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); - }); - - it('gets real-time data via async request', function() { - const setConfigSiteObj1 = { - name: 'www.audigent.com', - ext: { - segtax: '1', - taxprovider: '1' - }, - segment: [ - { - id: 'pubseg1' - }, - { - id: 'pubseg2' - } - ] - } - - config.setConfig({ - ortb2: { - site: { - content: { - data: [setConfigSiteObj1] - } - } - } - }); - - const rtdConfig = { - params: { - segmentCache: false, - usePubHalo: true, - requestParams: { - publisherId: 'testPub1' - } - } - }; - - let bidConfig = {}; - - const rtdUserObj1 = { - name: 'www.audigent.com', - ext: { - segtax: '1', - taxprovider: '1' - }, - segment: [ - { - id: 'pubseg1' - }, - { - id: 'pubseg2' - } - ] - }; - - const data = { - rtd: { - ortb2: { - user: { - data: [rtdUserObj1] - } - } - } - }; - - getDataFromLocalStorageStub.withArgs(HALOID_LOCAL_NAME).returns('testHaloId1'); - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - - let request = server.requests[0]; - let postData = JSON.parse(request.requestBody); - expect(postData.config).to.have.deep.property('publisherId', 'testPub1'); - expect(postData.userIds).to.have.deep.property('haloId', 'testHaloId1'); - - request.respond(200, responseHeader, JSON.stringify(data)); - - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); - }); - }); -}); diff --git a/test/spec/modules/hybridBidAdapter_spec.js b/test/spec/modules/hybridBidAdapter_spec.js index ffbc27293fb..6c98264c06f 100644 --- a/test/spec/modules/hybridBidAdapter_spec.js +++ b/test/spec/modules/hybridBidAdapter_spec.js @@ -15,7 +15,7 @@ function getSlotConfigs(mediaTypes, params) { describe('Hybrid.ai Adapter', function() { const PLACE_ID = '5af45ad34d506ee7acad0c26'; const bidderRequest = { - refererInfo: { referer: 'referer' } + refererInfo: { page: 'referer' } } const bannerMandatoryParams = { placeId: PLACE_ID, diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index a54542f7278..8c0f8ad9cf3 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -5,28 +5,36 @@ import { ID5_PRIVACY_STORAGE_NAME, ID5_STORAGE_NAME, id5IdSubmodule, - nbCacheName, storage, + nbCacheName, + storage, storeInLocalStorage, storeNbInCache, } from 'modules/id5IdSystem.js'; import {coreStorage, init, requestBidsHook, setSubmoduleRegistry} from 'modules/userId/index.js'; import {config} from 'src/config.js'; -import {server} from 'test/mocks/xhr.js'; import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; +import {uspDataHandler} from 'src/adapterManager.js'; import 'src/prebid.js'; import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; let expect = require('chai').expect; -describe('ID5 ID System', function() { +describe('ID5 ID System', function () { const ID5_MODULE_NAME = 'id5Id'; const ID5_EIDS_NAME = ID5_MODULE_NAME.toLowerCase(); const ID5_SOURCE = 'id5-sync.com'; const ID5_TEST_PARTNER_ID = 173; const ID5_ENDPOINT = `https://id5-sync.com/g/v2/${ID5_TEST_PARTNER_ID}.json`; + const ID5_API_CONFIG_URL = `https://id5-sync.com/api/config/prebid`; + const ID5_EXTENSIONS_ENDPOINT = 'https://extensions.id5-sync.com/test'; + const ID5_API_CONFIG = { + fetchCall: { + url: ID5_ENDPOINT + } + }; const ID5_NB_STORAGE_NAME = nbCacheName(ID5_TEST_PARTNER_ID); const ID5_STORED_ID = 'storedid5id'; const ID5_STORED_SIGNATURE = '123456'; @@ -58,6 +66,7 @@ describe('ID5 ID System', function() { } } } + function getId5ValueConfig(value) { return { name: ID5_MODULE_NAME, @@ -68,6 +77,7 @@ describe('ID5 ID System', function() { } } } + function getUserSyncConfig(userIds) { return { userSync: { @@ -76,12 +86,15 @@ describe('ID5 ID System', function() { } } } + function getFetchLocalStorageConfig() { return getUserSyncConfig([getId5FetchConfig(ID5_STORAGE_NAME, 'html5')]); } + function getValueConfig(value) { return getUserSyncConfig([getId5ValueConfig(value)]); } + function getAdUnitMock(code = 'adUnit-code') { return { code, @@ -91,15 +104,81 @@ describe('ID5 ID System', function() { }; } + function callSubmoduleGetId(config, consentData, cacheIdObj) { + return new Promise((resolve) => { + id5IdSubmodule.getId(config, consentData, cacheIdObj).callback((response) => { + resolve(response) + }) + }); + } + + class XhrServerMock { + constructor(server) { + this.currentRequestIdx = 0 + this.server = server + } + + expectFirstRequest() { + return this.#expectRequest(0); + } + + expectNextRequest() { + return this.#expectRequest(++this.currentRequestIdx) + } + + expectConfigRequest() { + return this.expectFirstRequest() + .then(configRequest => { + expect(configRequest.url).is.eq(ID5_API_CONFIG_URL); + expect(configRequest.method).is.eq('POST'); + return configRequest; + }) + } + + respondWithConfigAndExpectNext(configRequest, config = ID5_API_CONFIG) { + configRequest.respond(200, {'Content-Type': 'application/json'}, JSON.stringify(config)); + return this.expectNextRequest() + } + + expectFetchRequest() { + return this.expectConfigRequest() + .then(configRequest => { + return this.respondWithConfigAndExpectNext(configRequest, ID5_API_CONFIG); + }).then(request => { + expect(request.url).is.eq(ID5_API_CONFIG.fetchCall.url); + expect(request.method).is.eq('POST'); + return request; + }) + } + + #expectRequest(index) { + let server = this.server + return new Promise(function (resolve) { + (function waitForCondition() { + if (server.requests && server.requests.length > index) return resolve(server.requests[index]); + setTimeout(waitForCondition, 30); + })(); + }) + .then(request => { + return request + }); + } + + hasReceivedAnyRequest() { + let requests = this.server.requests; + return requests && requests.length > 0; + } + } + before(() => { hook.ready(); }); - describe('Check for valid publisher config', function() { - it('should fail with invalid config', function() { + describe('Check for valid publisher config', function () { + it('should fail with invalid config', function () { // no config - expect(id5IdSubmodule.getId()).to.be.eq(undefined); - expect(id5IdSubmodule.getId({ })).to.be.eq(undefined); + expect(id5IdSubmodule.getId()).is.eq(undefined); + expect(id5IdSubmodule.getId({})).is.eq(undefined); // valid params, invalid storage expect(id5IdSubmodule.getId({ params: { partner: 123 } })).to.be.eq(undefined); @@ -113,7 +192,7 @@ describe('ID5 ID System', function() { expect(id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 'abc' } })).to.be.eq(undefined); }); - it('should warn with non-recommended storage params', function() { + it('should warn with non-recommended storage params', function () { let logWarnStub = sinon.stub(utils, 'logWarn'); id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 123 } }); @@ -126,189 +205,457 @@ describe('ID5 ID System', function() { }); }); - describe('Xhr Requests from getId()', function() { - const responseHeader = { 'Content-Type': 'application/json' }; - let callbackSpy = sinon.spy(); + describe('Xhr Requests from getId()', function () { + const responseHeader = {'Content-Type': 'application/json'}; - beforeEach(function() { - callbackSpy.resetHistory(); + beforeEach(function () { }); - afterEach(function () { + afterEach(function () { + uspDataHandler.reset() }); it('should call the ID5 server and handle a valid response', function () { - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, undefined).callback; - submoduleCallback(callbackSpy); - - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(request.withCredentials).to.be.true; - expect(requestBody.partner).to.eq(ID5_TEST_PARTNER_ID); - expect(requestBody.o).to.eq('pbjs'); - expect(requestBody.pd).to.be.undefined; - expect(requestBody.s).to.be.undefined; - expect(requestBody.provider).to.be.undefined - expect(requestBody.v).to.eq('$prebid.version$'); - expect(requestBody.gdpr).to.exist; - expect(requestBody.gdpr_consent).to.be.undefined; - expect(requestBody.us_privacy).to.be.undefined; - - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(callbackSpy.calledOnce).to.be.true; - expect(callbackSpy.lastCall.lastArg).to.deep.equal(ID5_JSON_RESPONSE); + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let config = getId5FetchConfig(); + let submoduleResponse = callSubmoduleGetId(config, undefined, undefined); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(fetchRequest.url).to.contain(ID5_ENDPOINT); + expect(fetchRequest.withCredentials).is.true; + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.o).is.eq('pbjs'); + expect(requestBody.pd).is.undefined; + expect(requestBody.s).is.undefined; + expect(requestBody.provider).is.undefined + expect(requestBody.v).is.eq('$prebid.version$'); + expect(requestBody.gdpr).is.eq(0); + expect(requestBody.gdpr_consent).is.undefined; + expect(requestBody.us_privacy).is.undefined; + expect(requestBody.storage).is.deep.eq(config.storage) + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + .then(submoduleResponse => { + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + }); + }); + + it('should call the ID5 server with gdpr data ', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let consentData = { + gdprApplies: true, + consentString: 'consentString' + } + + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.gdpr).to.eq(1); + expect(requestBody.gdpr_consent).is.eq(consentData.consentString); + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + .then(submoduleResponse => { + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + }); + }); + + it('should call the ID5 server without gdpr data when gdpr not applies ', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let consentData = { + gdprApplies: false, + consentString: 'consentString' + } + + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.gdpr).to.eq(0); + expect(requestBody.gdpr_consent).is.undefined + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + .then(submoduleResponse => { + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + }); + }); + + it('should call the ID5 server with us privacy consent', function () { + let usPrivacyString = '1YN-'; + uspDataHandler.setConsentData(usPrivacyString) + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let consentData = { + gdprApplies: true, + consentString: 'consentString' + } + + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.us_privacy).to.eq(usPrivacyString); + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + .then(submoduleResponse => { + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + }); }); it('should call the ID5 server with no signature field when no stored object', function () { - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, undefined).callback; - submoduleCallback(callbackSpy); + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.s).is.undefined; + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + }); - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(requestBody.s).to.be.undefined; + it('should call the ID5 server for config with submodule config object', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let id5FetchConfig = getId5FetchConfig(); + id5FetchConfig.params.extraParam = { + x: 'X', + y: { + a: 1, + b: '3' + } + } + let submoduleResponse = callSubmoduleGetId(id5FetchConfig, undefined, undefined); + + return xhrServerMock.expectConfigRequest() + .then(configRequest => { + let requestBody = JSON.parse(configRequest.requestBody) + expect(requestBody).is.deep.eq(id5FetchConfig) + return xhrServerMock.respondWithConfigAndExpectNext(configRequest) + }) + .then(fetchRequest => { + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + }); - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + it('should call the ID5 server for config with overridden url', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let id5FetchConfig = getId5FetchConfig(); + id5FetchConfig.params.configUrl = 'http://localhost/x/y/z' + + let submoduleResponse = callSubmoduleGetId(id5FetchConfig, undefined, undefined); + + return xhrServerMock.expectFirstRequest() + .then(configRequest => { + expect(configRequest.url).is.eq('http://localhost/x/y/z') + return xhrServerMock.respondWithConfigAndExpectNext(configRequest) + }) + .then(fetchRequest => { + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) }); - it('should call the ID5 server with signature field from stored object', function () { - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; - submoduleCallback(callbackSpy); + it('should call the ID5 server with additional data when provided', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); + + return xhrServerMock.expectConfigRequest() + .then(configRequest => { + return xhrServerMock.respondWithConfigAndExpectNext(configRequest, { + fetchCall: { + url: ID5_ENDPOINT, + overrides: { + arg1: '123', + arg2: { + x: '1', + y: 2 + } + } + } + }); + }) + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.o).is.eq('pbjs'); + expect(requestBody.v).is.eq('$prebid.version$'); + expect(requestBody.arg1).is.eq('123') + expect(requestBody.arg2).is.deep.eq({ + x: '1', + y: 2 + }) + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + }); - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(requestBody.s).to.eq(ID5_STORED_SIGNATURE); + it('should call the ID5 server with extensions', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); + + return xhrServerMock.expectConfigRequest() + .then(configRequest => { + return xhrServerMock.respondWithConfigAndExpectNext(configRequest, { + fetchCall: { + url: ID5_ENDPOINT + }, + extensionsCall: { + url: ID5_EXTENSIONS_ENDPOINT, + method: 'GET' + } + }); + }) + .then(extensionsRequest => { + expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT) + expect(extensionsRequest.method).is.eq('GET') + extensionsRequest.respond(200, responseHeader, JSON.stringify({ + lb: 'ex' + })) + return xhrServerMock.expectNextRequest(); + }) + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.o).is.eq('pbjs'); + expect(requestBody.v).is.eq('$prebid.version$'); + expect(requestBody.extensions).is.deep.eq({ + lb: 'ex' + }) + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + }); + + it('should call the ID5 server with extensions fetched with POST', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); + + return xhrServerMock.expectConfigRequest() + .then(configRequest => { + return xhrServerMock.respondWithConfigAndExpectNext(configRequest, { + fetchCall: { + url: ID5_ENDPOINT + }, + extensionsCall: { + url: ID5_EXTENSIONS_ENDPOINT, + method: 'POST', + body: { + x: '1', + y: 2 + } + } + }); + }) + .then(extensionsRequest => { + expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT) + expect(extensionsRequest.method).is.eq('POST') + let requestBody = JSON.parse(extensionsRequest.requestBody) + expect(requestBody).is.deep.eq({ + x: '1', + y: 2 + }) + extensionsRequest.respond(200, responseHeader, JSON.stringify({ + lb: 'post', + })) + return xhrServerMock.expectNextRequest(); + }) + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.o).is.eq('pbjs'); + expect(requestBody.v).is.eq('$prebid.version$'); + expect(requestBody.extensions).is.deep.eq({ + lb: 'post' + }) + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + }); - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + it('should call the ID5 server with signature field from stored object', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) }); it('should call the ID5 server with pd field when pd config is set', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) const pubData = 'b50ca08271795a8e7e4012813f23d505193d75c0f2e2bb99baa63aa822f66ed3'; let id5Config = getId5FetchConfig(); id5Config.params.pd = pubData; - let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; - submoduleCallback(callbackSpy); - - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(requestBody.pd).to.eq(pubData); + let submoduleResponse = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.pd).is.eq(pubData); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse; + }) }); it('should call the ID5 server with no pd field when pd config is not set', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) let id5Config = getId5FetchConfig(); id5Config.params.pd = undefined; - let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; - submoduleCallback(callbackSpy); + let submoduleResponse = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(requestBody.pd).to.be.undefined; - - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.pd).is.undefined; + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse; + }) }); it('should call the ID5 server with nb=1 when no stored value exists and reset after', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; - submoduleCallback(callbackSpy); - - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(requestBody.nbPage).to.eq(1); - - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.nbPage).is.eq(1); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + .then(() => { + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); + }) }); it('should call the ID5 server with incremented nb when stored value exists and reset after', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) storeNbInCache(ID5_TEST_PARTNER_ID, 1); - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; - submoduleCallback(callbackSpy); - - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(requestBody.nbPage).to.eq(2); - - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.nbPage).is.eq(2); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }) + .then(() => { + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); + }) }); it('should call the ID5 server with ab_testing object when abTesting is turned on', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) let id5Config = getId5FetchConfig(); - id5Config.params.abTesting = { enabled: true, controlGroupPct: 0.234 } - - let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; - submoduleCallback(callbackSpy); + id5Config.params.abTesting = {enabled: true, controlGroupPct: 0.234} - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(requestBody.ab_testing.enabled).to.eq(true); - expect(requestBody.ab_testing.control_group_pct).to.eq(0.234); + let submoduleResponse = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.ab_testing.enabled).is.eq(true); + expect(requestBody.ab_testing.control_group_pct).is.eq(0.234); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse; + }); }); it('should call the ID5 server without ab_testing object when abTesting is turned off', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) let id5Config = getId5FetchConfig(); - id5Config.params.abTesting = { enabled: false, controlGroupPct: 0.55 } - - let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; - submoduleCallback(callbackSpy); + id5Config.params.abTesting = {enabled: false, controlGroupPct: 0.55} - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(requestBody.ab_testing).to.be.undefined; + let submoduleResponse = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.ab_testing).is.undefined; + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }); }); it('should call the ID5 server without ab_testing when when abTesting is not set', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) let id5Config = getId5FetchConfig(); - let submoduleCallback = id5IdSubmodule.getId(id5Config, undefined, ID5_STORED_OBJ).callback; - submoduleCallback(callbackSpy); - - let request = server.requests[0]; - let requestBody = JSON.parse(request.requestBody); - expect(requestBody.ab_testing).to.be.undefined; + let submoduleResponse = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.ab_testing).is.undefined; + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }); }); it('should store the privacy object from the ID5 server response', function () { - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; - submoduleCallback(callbackSpy); + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); - let request = server.requests[0]; - - let responseObject = utils.deepClone(ID5_JSON_RESPONSE); - responseObject.privacy = { + const privacy = { jurisdiction: 'gdpr', id5_consent: true }; - request.respond(200, responseHeader, JSON.stringify(responseObject)); - expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).to.be.eq(JSON.stringify(responseObject.privacy)); - coreStorage.removeDataFromLocalStorage(ID5_PRIVACY_STORAGE_NAME); + + return xhrServerMock.expectFetchRequest() + .then(request => { + let responseObject = utils.deepClone(ID5_JSON_RESPONSE); + responseObject.privacy = privacy; + request.respond(200, responseHeader, JSON.stringify(responseObject)); + return submoduleResponse + }) + .then(() => { + expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).is.eq(JSON.stringify(privacy)); + coreStorage.removeDataFromLocalStorage(ID5_PRIVACY_STORAGE_NAME); + }) }); it('should not store a privacy object if not part of ID5 server response', function () { + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) coreStorage.removeDataFromLocalStorage(ID5_PRIVACY_STORAGE_NAME); - let submoduleCallback = id5IdSubmodule.getId(getId5FetchConfig(), undefined, ID5_STORED_OBJ).callback; - submoduleCallback(callbackSpy); - - let request = server.requests[0]; - - request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).to.be.null; + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(request => { + let responseObject = utils.deepClone(ID5_JSON_RESPONSE); + responseObject.privacy = undefined; + request.respond(200, responseHeader, JSON.stringify(responseObject)); + return submoduleResponse + }) + .then(() => { + expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).is.null; + }); }); describe('when legacy cookies are set', () => { @@ -327,11 +674,11 @@ describe('ID5 ID System', function() { }) }); - describe('Request Bids Hook', function() { + describe('Request Bids Hook', function () { let adUnits; let sandbox; - beforeEach(function() { + beforeEach(function () { sandbox = sinon.sandbox.create(); mockGdprConsent(sandbox); sinon.stub(events, 'getEvents').returns([]); @@ -340,7 +687,7 @@ describe('ID5 ID System', function() { coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); adUnits = [getAdUnitMock()]; }); - afterEach(function() { + afterEach(function () { events.getEvents.restore(); coreStorage.removeDataFromLocalStorage(ID5_STORAGE_NAME); coreStorage.removeDataFromLocalStorage(`${ID5_STORAGE_NAME}_last`); @@ -359,8 +706,8 @@ describe('ID5 ID System', function() { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); - expect(bid.userId.id5id.uid).to.equal(ID5_STORED_ID); - expect(bid.userIdAsEids[0]).to.deep.equal({ + expect(bid.userId.id5id.uid).is.equal(ID5_STORED_ID); + expect(bid.userIdAsEids[0]).is.deep.equal({ source: ID5_SOURCE, uids: [{ id: ID5_STORED_ID, @@ -373,7 +720,7 @@ describe('ID5 ID System', function() { }); }); done(); - }, { adUnits }); + }, {adUnits}); }); it('should add config value ID to bids', function (done) { @@ -385,15 +732,15 @@ describe('ID5 ID System', function() { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); - expect(bid.userId.id5id.uid).to.equal(ID5_STORED_ID); - expect(bid.userIdAsEids[0]).to.deep.equal({ + expect(bid.userId.id5id.uid).is.equal(ID5_STORED_ID); + expect(bid.userIdAsEids[0]).is.deep.equal({ source: ID5_SOURCE, - uids: [{ id: ID5_STORED_ID, atype: 1 }] + uids: [{id: ID5_STORED_ID, atype: 1}] }); }); }); done(); - }, { adUnits }); + }, {adUnits}); }); it('should set nb=1 in cache when no stored nb value exists and cached ID', function (done) { @@ -405,7 +752,7 @@ describe('ID5 ID System', function() { config.setConfig(getFetchLocalStorageConfig()); requestBidsHook((adUnitConfig) => { - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(1); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(1); done() }, {adUnits}); }); @@ -419,19 +766,20 @@ describe('ID5 ID System', function() { config.setConfig(getFetchLocalStorageConfig()); requestBidsHook(() => { - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(2); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); done() }, {adUnits}); }); it('should call ID5 servers with signature and incremented nb post auction if refresh needed', function () { - storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + let xhrServerMock = new XhrServerMock(sinon.createFakeServer()) + let initialLocalStorageValue = JSON.stringify(ID5_STORED_OBJ); + storeInLocalStorage(ID5_STORAGE_NAME, initialLocalStorageValue, 1); storeInLocalStorage(`${ID5_STORAGE_NAME}_last`, expDaysStr(-1), 1); - storeNbInCache(ID5_TEST_PARTNER_ID, 1); + storeNbInCache(ID5_TEST_PARTNER_ID, 1); let id5Config = getFetchLocalStorageConfig(); id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; - init(config); setSubmoduleRegistry([id5IdSubmodule]); config.setConfig(id5Config); @@ -441,53 +789,62 @@ describe('ID5 ID System', function() { resolve() }, {adUnits}); }).then(() => { - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(2); - expect(server.requests).to.be.empty; + expect(xhrServerMock.hasReceivedAnyRequest()).is.false; events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - return new Promise((resolve) => setTimeout(resolve)) - }).then(() => { - let request = server.requests[0]; + return xhrServerMock.expectFetchRequest() + }).then(request => { let requestBody = JSON.parse(request.requestBody); - expect(request.url).to.contain(ID5_ENDPOINT); - expect(requestBody.s).to.eq(ID5_STORED_SIGNATURE); - expect(requestBody.nbPage).to.eq(2); - - const responseHeader = { 'Content-Type': 'application/json' }; + expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); + expect(requestBody.nbPage).is.eq(2); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); + const responseHeader = {'Content-Type': 'application/json'}; request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - expect(decodeURIComponent(getFromLocalStorage(ID5_STORAGE_NAME))).to.be.eq(JSON.stringify(ID5_JSON_RESPONSE)); - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).to.be.eq(0); + return new Promise(function (resolve) { + (function waitForCondition() { + if (getFromLocalStorage(ID5_STORAGE_NAME) !== initialLocalStorageValue) return resolve(); + setTimeout(waitForCondition, 30); + })(); + }) + }).then(() => { + expect(decodeURIComponent(getFromLocalStorage(ID5_STORAGE_NAME))).is.eq(JSON.stringify(ID5_JSON_RESPONSE)); + expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); }) }); }); - describe('Decode stored object', function() { - const expectedDecodedObject = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE } } }; + describe('Decode stored object', function () { + const expectedDecodedObject = {id5id: {uid: ID5_STORED_ID, ext: {linkType: ID5_STORED_LINK_TYPE}}}; - it('should properly decode from a stored object', function() { - expect(id5IdSubmodule.decode(ID5_STORED_OBJ, getId5FetchConfig())).to.deep.equal(expectedDecodedObject); + it('should properly decode from a stored object', function () { + expect(id5IdSubmodule.decode(ID5_STORED_OBJ, getId5FetchConfig())).is.deep.equal(expectedDecodedObject); }); - it('should return undefined if passed a string', function() { - expect(id5IdSubmodule.decode('somestring', getId5FetchConfig())).to.eq(undefined); + it('should return undefined if passed a string', function () { + expect(id5IdSubmodule.decode('somestring', getId5FetchConfig())).is.eq(undefined); }); }); - describe('A/B Testing', function() { - const expectedDecodedObjectWithIdAbOff = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE } } }; - const expectedDecodedObjectWithIdAbOn = { id5id: { uid: ID5_STORED_ID, ext: { linkType: ID5_STORED_LINK_TYPE, abTestingControlGroup: false } } }; - const expectedDecodedObjectWithoutIdAbOn = { id5id: { uid: '', ext: { linkType: 0, abTestingControlGroup: true } } }; + describe('A/B Testing', function () { + const expectedDecodedObjectWithIdAbOff = {id5id: {uid: ID5_STORED_ID, ext: {linkType: ID5_STORED_LINK_TYPE}}}; + const expectedDecodedObjectWithIdAbOn = { + id5id: { + uid: ID5_STORED_ID, + ext: {linkType: ID5_STORED_LINK_TYPE, abTestingControlGroup: false} + } + }; + const expectedDecodedObjectWithoutIdAbOn = {id5id: {uid: '', ext: {linkType: 0, abTestingControlGroup: true}}}; let testConfig, storedObject; - beforeEach(function() { + beforeEach(function () { testConfig = getId5FetchConfig(); storedObject = utils.deepClone(ID5_STORED_OBJ); }); - describe('A/B Testing Config is Set', function() { + describe('A/B Testing Config is Set', function () { let randStub; - beforeEach(function() { - randStub = sinon.stub(Math, 'random').callsFake(function() { + beforeEach(function () { + randStub = sinon.stub(Math, 'random').callsFake(function () { return 0.25; }); }); @@ -495,39 +852,39 @@ describe('ID5 ID System', function() { randStub.restore(); }); - describe('Decode', function() { + describe('Decode', function () { let logErrorSpy; - beforeEach(function() { + beforeEach(function () { logErrorSpy = sinon.spy(utils, 'logError'); }); - afterEach(function() { + afterEach(function () { logErrorSpy.restore(); }); it('should not set abTestingControlGroup extension when A/B testing is off', function () { let decoded = id5IdSubmodule.decode(storedObject, testConfig); - expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + expect(decoded).is.deep.equal(expectedDecodedObjectWithIdAbOff); }); it('should set abTestingControlGroup to false when A/B testing is on but in normal group', function () { - storedObject.ab_testing = { result: 'normal' }; + storedObject.ab_testing = {result: 'normal'}; let decoded = id5IdSubmodule.decode(storedObject, testConfig); - expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOn); + expect(decoded).is.deep.equal(expectedDecodedObjectWithIdAbOn); }); it('should not expose ID when everyone is in control group', function () { - storedObject.ab_testing = { result: 'control' }; + storedObject.ab_testing = {result: 'control'}; storedObject.universal_uid = ''; storedObject.link_type = 0; let decoded = id5IdSubmodule.decode(storedObject, testConfig); - expect(decoded).to.deep.equal(expectedDecodedObjectWithoutIdAbOn); + expect(decoded).is.deep.equal(expectedDecodedObjectWithoutIdAbOn); }); it('should log A/B testing errors', function () { - storedObject.ab_testing = { result: 'error' }; + storedObject.ab_testing = {result: 'error'}; let decoded = id5IdSubmodule.decode(storedObject, testConfig); - expect(decoded).to.deep.equal(expectedDecodedObjectWithIdAbOff); + expect(decoded).is.deep.equal(expectedDecodedObjectWithIdAbOff); sinon.assert.calledOnce(logErrorSpy); }); }); diff --git a/test/spec/modules/idWardRtdProvider_spec.js b/test/spec/modules/idWardRtdProvider_spec.js index 949365baec6..924a3794c7b 100644 --- a/test/spec/modules/idWardRtdProvider_spec.js +++ b/test/spec/modules/idWardRtdProvider_spec.js @@ -45,7 +45,11 @@ describe('idWardRtdProvider', function() { } }; - const bidConfig = {}; + const bidConfig = { + ortb2Fragments: { + global: {} + } + }; const rtdUserObj1 = { name: 'id-ward.com', @@ -65,9 +69,8 @@ describe('idWardRtdProvider', function() { getDataFromLocalStorageStub.withArgs('cohort_ids') .returns(JSON.stringify(['TCZPQOWPEJG3MJOTUQUF793A', '93SUG3H540WBJMYNT03KX8N3'])); - expect(config.getConfig().ortb2).to.be.undefined; getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2.user.data).to.deep.include.members([rtdUserObj1]); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); }); it('do not set rtd if local storage empty', function() { diff --git a/test/spec/modules/idxBidAdapter_spec.js b/test/spec/modules/idxBidAdapter_spec.js new file mode 100644 index 00000000000..4721b0d4b6e --- /dev/null +++ b/test/spec/modules/idxBidAdapter_spec.js @@ -0,0 +1,103 @@ +import { expect } from 'chai' +import { spec } from 'modules/idxBidAdapter.js' + +const BIDDER_CODE = 'idx' +const ENDPOINT_URL = 'https://dev-event.dxmdp.com/rest/api/v1/bid' +const DEFAULT_PRICE = 1 +const DEFAULT_CURRENCY = 'USD' +const DEFAULT_BANNER_WIDTH = 300 +const DEFAULT_BANNER_HEIGHT = 250 + +describe('idxBidAdapter', function () { + describe('isBidRequestValid', function () { + let validBid = { + bidder: BIDDER_CODE, + mediaTypes: { + banner: { + sizes: [[DEFAULT_BANNER_WIDTH, DEFAULT_BANNER_HEIGHT]] + } + } + } + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, validBid) + bid.mediaTypes = {} + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + describe('buildRequests', function () { + let bidRequests = [ + { + bidder: BIDDER_CODE, + bidId: 'asdf12345', + mediaTypes: { + banner: { + sizes: [[DEFAULT_BANNER_WIDTH, DEFAULT_BANNER_HEIGHT]] + } + }, + } + ] + let bidderRequest = { + bidderCode: BIDDER_CODE, + bidderRequestId: '12345asdf', + bids: [ + { + ...bidRequests[0] + } + ], + } + + it('sends video bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest) + expect(request.url).to.equal(ENDPOINT_URL) + expect(request.method).to.equal('POST') + }) + }) + describe('interpretResponse', function () { + it('should get correct bid response', function () { + let response = { + id: 'f6adb85f-4e19-45a0-b41e-2a5b9a48f23a', + seatbid: [ + { + bid: [ + { + id: '123', + impid: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + price: DEFAULT_PRICE, + adm: 'hi', + cid: 'test_cid', + crid: 'test_banner_crid', + w: DEFAULT_BANNER_WIDTH, + h: DEFAULT_BANNER_HEIGHT, + adomain: [], + } + ], + seat: BIDDER_CODE + } + ], + } + + let expectedResponse = [ + { + requestId: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + cpm: DEFAULT_PRICE, + width: DEFAULT_BANNER_WIDTH, + height: DEFAULT_BANNER_HEIGHT, + creativeId: 'test_banner_crid', + ad: 'hi', + currency: DEFAULT_CURRENCY, + netRevenue: true, + ttl: 300, + meta: { advertiserDomains: [] }, + } + ] + let result = spec.interpretResponse({ body: response }) + + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])) + }) + }) +}) diff --git a/test/spec/modules/idxIdSystem_spec.js b/test/spec/modules/idxIdSystem_spec.js index 7d008ef0d28..56e1c709c8b 100644 --- a/test/spec/modules/idxIdSystem_spec.js +++ b/test/spec/modules/idxIdSystem_spec.js @@ -94,8 +94,8 @@ describe('IDx ID System', () => { adUnits = [getAdUnitMock()]; init(config); setSubmoduleRegistry([idxIdSubmodule]); - config.setConfig(getConfigMock()); getCookieStub.withArgs(IDX_COOKIE_NAME).returns(IDX_COOKIE_STORED); + config.setConfig(getConfigMock()); }); afterEach(() => { diff --git a/test/spec/modules/imRtdProvider_spec.js b/test/spec/modules/imRtdProvider_spec.js index 6d92440a144..89328b91529 100644 --- a/test/spec/modules/imRtdProvider_spec.js +++ b/test/spec/modules/imRtdProvider_spec.js @@ -27,7 +27,8 @@ describe('imRtdProvider', function () { const moduleConfig = { params: { cid: 5126, - setGptKeyValues: true + setGptKeyValues: true, + maxSegments: 2 } } @@ -50,7 +51,6 @@ describe('imRtdProvider', function () { describe('getBidderFunction', function () { const assumedBidder = [ - 'ix', 'pubmatic', 'fluct' ]; @@ -61,11 +61,11 @@ describe('imRtdProvider', function () { it(`should return bid with correct key data: ${bidderName}`, function () { const bid = {bidder: bidderName}; - expect(getBidderFunction(bidderName)(bid, {'im_segments': ['12345', '67890']})).to.equal(bid); + expect(getBidderFunction(bidderName)(bid, {'im_segments': ['12345', '67890']}, {params: {}})).to.equal(bid); }); it(`should return bid without data: ${bidderName}`, function () { const bid = {bidder: bidderName}; - expect(getBidderFunction(bidderName)(bid, '')).to.equal(bid); + expect(getBidderFunction(bidderName)(bid, '', {params: {}})).to.equal(bid); }); }); it(`should return null with unexpected bidder`, function () { @@ -74,7 +74,7 @@ describe('imRtdProvider', function () { describe('fluct bidder function', function () { it('should return a bid w/o im_segments if not any exists', function () { const bid = {bidder: 'fluct'}; - expect(getBidderFunction('fluct')(bid, '')).to.eql(bid); + expect(getBidderFunction('fluct')(bid, '', {params: {}})).to.eql(bid); }); it('should return a bid w/ im_segments if any exists', function () { const bid = { @@ -85,7 +85,11 @@ describe('imRtdProvider', function () { } } }; - expect(getBidderFunction('fluct')(bid, {im_segments: ['12345', '67890']})) + expect(getBidderFunction('fluct')( + bid, + {im_segments: ['12345', '67890', '09876']}, + {params: {maxSegments: 2}} + )) .to.eql( { bidder: 'fluct', diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index b6e5ab86de5..19e91cbde96 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -141,7 +141,8 @@ describe('Improve Digital Adapter Tests', function () { const bidderRequestReferrer = { bids: [simpleBidRequest], refererInfo: { - referer: 'https://blah.com/test.html', + page: 'https://blah.com/test.html', + domain: 'blah.com' }, }; @@ -247,7 +248,7 @@ describe('Improve Digital Adapter Tests', function () { { id: '33e9500b21129f', native: { - request: '{"assets":[{"id":3,"required":1,"data":{"type":2}}]}', + request: '{"eventtrackers":[{"event":1,"methods":[1,2]}],"assets":[{"id":3,"required":1,"data":{"type":2}}]}', ver: '1.2' }, secure: 0, @@ -273,46 +274,46 @@ describe('Improve Digital Adapter Tests', function () { }); it('should make a well-formed native request', function () { - const payload = JSON.parse(spec.buildRequests([nativeBidRequest])[0].data); + const payload = JSON.parse(spec.buildRequests([nativeBidRequest], {})[0].data); expect(payload.imp[0].native).to.deep.equal({ ver: '1.2', - request: '{\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":3,\"required\":1,\"data\":{\"type\":2}}]}' + request: '{"eventtrackers":[{"event":1,"methods":[1,2]}],"assets":[{"id":0,"required":1,"title":{"len":140}},{"id":3,"required":1,"data":{"type":2}}]}' }); }); it('should not make native request when nativeParams is undefined', function () { const request = deepClone(nativeBidRequest); delete request.nativeParams; - const payload = JSON.parse(spec.buildRequests([request])[0].data); + const payload = JSON.parse(spec.buildRequests([request], {})[0].data); expect(payload.imp[0].native).to.not.exist; }); it('should not make native request when no assets', function () { const request = deepClone(nativeBidRequest); request.nativeParams = {}; - const payload = JSON.parse(spec.buildRequests([request])[0].data); + const payload = JSON.parse(spec.buildRequests([request], {})[0].data); expect(payload.imp[0].native).to.not.exist; }); it('should make a well-formed native request', function () { - const payload = JSON.parse(spec.buildRequests([nativeBidRequest])[0].data); + const payload = JSON.parse(spec.buildRequests([nativeBidRequest], {})[0].data); expect(payload.imp[0].native).to.deep.equal({ ver: '1.2', - request: '{\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":3,\"required\":1,\"data\":{\"type\":2}}]}' + request: '{"eventtrackers":[{"event":1,"methods":[1,2]}],"assets":[{"id":0,"required":1,"title":{"len":140}},{"id":3,"required":1,"data":{"type":2}}]}' }); }); it('should not make native request when nativeParams is undefined', function () { const request = deepClone(nativeBidRequest); delete request.nativeParams; - const payload = JSON.parse(spec.buildRequests([request])[0].data); + const payload = JSON.parse(spec.buildRequests([request], {})[0].data); expect(payload.imp[0].native).to.not.exist; }); it('should not make native request when no assets', function () { const request = deepClone(nativeBidRequest); request.nativeParams = {}; - const payload = JSON.parse(spec.buildRequests([request])[0].data); + const payload = JSON.parse(spec.buildRequests([request], {})[0].data); expect(payload.imp[0].native).to.not.exist; }); @@ -493,7 +494,7 @@ describe('Improve Digital Adapter Tests', function () { skipafter: 30 } bidRequest.params.video = videoTest; - let request = spec.buildRequests([bidRequest])[0]; + let request = spec.buildRequests([bidRequest], {})[0]; let payload = JSON.parse(request.data); expect(payload.imp[0].video.skip).to.equal(1); expect(payload.imp[0].video.skipmin).to.equal(5); @@ -502,7 +503,7 @@ describe('Improve Digital Adapter Tests', function () { // 0 - leave out skipmin and skipafter videoTest.skip = 0; bidRequest.params.video = videoTest; - request = spec.buildRequests([bidRequest])[0]; + request = spec.buildRequests([bidRequest], {})[0]; payload = JSON.parse(request.data); expect(payload.imp[0].video.skip).to.equal(0); expect(payload.imp[0].video.skipmin).to.not.exist; @@ -511,7 +512,7 @@ describe('Improve Digital Adapter Tests', function () { // other videoTest.skip = 'blah'; bidRequest.params.video = videoTest; - request = spec.buildRequests([bidRequest])[0]; + request = spec.buildRequests([bidRequest], {})[0]; payload = JSON.parse(request.data); expect(payload.imp[0].video.skip).to.not.exist; expect(payload.imp[0].video.skipmin).to.not.exist; @@ -529,7 +530,7 @@ describe('Improve Digital Adapter Tests', function () { const videoTestInvParam = Object.assign({}, videoTest); videoTestInvParam.blah = 1; bidRequest.params.video = videoTestInvParam; - let request = spec.buildRequests([bidRequest])[0]; + let request = spec.buildRequests([bidRequest], {})[0]; let payload = JSON.parse(request.data); expect(payload.imp[0].video.blah).not.to.exist; }); @@ -537,7 +538,7 @@ describe('Improve Digital Adapter Tests', function () { it('should set video params for outstream', function() { const bidRequest = deepClone(outstreamBidRequest); bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest])[0]; + const request = spec.buildRequests([bidRequest], {})[0]; const payload = JSON.parse(request.data); expect(payload.imp[0].video).to.deep.equal({...{ mimes: ['video/mp4'], @@ -551,7 +552,7 @@ describe('Improve Digital Adapter Tests', function () { it('should set video params for multi-format', function() { const bidRequest = deepClone(multiFormatBidRequest); bidRequest.params.video = videoParams; - const request = spec.buildRequests([bidRequest])[0]; + const request = spec.buildRequests([bidRequest], {})[0]; const payload = JSON.parse(request.data); const testVideoParams = Object.assign({ placement: OUTSTREAM_TYPE, @@ -696,9 +697,8 @@ describe('Improve Digital Adapter Tests', function () { }); it('should not set site when app is defined in FPD', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('ortb2.app').returns({ content: 'XYZ' }); - let request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; + const ortb2 = {app: {content: 'XYZ'}}; + let request = spec.buildRequests([simpleBidRequest], {...bidderRequest, ortb2})[0]; let payload = JSON.parse(request.data); expect(payload.site).does.not.exist; expect(payload.app).does.exist; @@ -735,32 +735,14 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); expect(payload.site.domain).does.exist.and.equal('blah.com'); - getConfigStub.withArgs('ortb2.site').returns({ - content: 'ZZZ', - }); - request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; + const ortb2 = {site: {content: 'ZZZ'}}; + request = spec.buildRequests([simpleBidRequest], {...bidderRequestReferrer, ortb2})[0]; payload = JSON.parse(request.data); expect(payload.site.content).does.exist.and.equal('ZZZ'); expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); expect(payload.site.domain).does.exist.and.equal('blah.com'); }); - it('should set pageUrl as site param', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('pageUrl').returns('https://improvidigital.com/test-page'); - let request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; - let payload = JSON.parse(request.data); - expect(payload.site.page).does.exist.and.equal('https://improvidigital.com/test-page'); - expect(payload.site.domain).does.exist.and.equal('improvidigital.com'); - getConfigStub.reset(); - - getConfigStub.withArgs('pageUrl').returns(undefined); - request = spec.buildRequests([simpleBidRequest], bidderRequestReferrer)[0]; - payload = JSON.parse(request.data); - expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); - expect(payload.site.domain).does.exist.and.equal('blah.com'); - }); - it('should set site when app not available', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('app').returns(undefined); @@ -948,7 +930,7 @@ describe('Improve Digital Adapter Tests', function () { 'exp': 120, 'id': '52098fad-20c1-476b-a4fa-41e275e5a4a5', 'price': 1.8600000000000003, - 'adm': "{\"ver\":\"1.1\",\"imptrackers\":[\"https://secure.adnxs.com/imptr?id=52311&t=2\",\"https://euw-ice.360yield.com/imp_pixel?ic=hcUBlCANx1FabHBf6FR2gC7UO4xEyXahdZAn0-B5qL-bb3A74BJ1smyWIyW7IWcC0SOjSXzVpevTHXxTqJ.sf.Qhahyy6tSo.0j1QWfXlH8sM4-8vKWjMjw-x.IrJJNlwkQ0s1CdwcwTefcLXm5l2E-W19VhACuV7f3mgrZMNjiSw.SjJAfyPC3SIyAMRjYfj53UmjriQ46T7lhmkqxK8wHmksYCdbZc3PZESk8NWl28sxdjNvnYYCFMcJbeav.LOLabyTXfwy-1cEPbQs.IKMRZIKaqccTDPV3wOtzbNv0jQzatd3Nnv-PGFQcjQ-GW3i27W04Fws4kodpFSn-B6VwZAjzLzoyd5gBncyRnAyCplEbgHU5sZ1IyKHWjgCl3ZtRIK5vqrRD5D-xqgSnOi7-phG.CqZWDZ4bMDSfQg2ZnbvUTyGKcEl0WR59dW5izTMV4Fjizcrvr5T-t.zMbGwz.hGnmLIyhTqh.IcwW.GiDLVExlDlix5S1LXIWVsSyrQ==\"],\"assets\":[{\"id\":1,\"data\":{\"value\":\"ImproveDigital\",\"type\":1}},{\"id\":3,\"data\":{\"value\":\"Test content.\",\"type\":2}},{\"id\":0,\"title\":{\"text\":\"Sample Prebid Test Title\"}}],\"link\":{\"url\":\"https://euw-ice.360yield.com/click/hcUBlHOV7YhVse8RyBa0ajjyPa9Vt17e4g-1m3cRj3E67vq-RYux.SiUeAmBfNBcoOqkUc6A15AWmi4yFu5K-BdkaYjildyyk7fNLyR6hWr411kv4vrFwm5jrIBceuHS6K8oN69f.uCo8zGTdR2TbSlldwcpahQPlufZU.6VaMsu4IC53uEiUT5vb7kAw6TTlxuGBNq6zaGryiWEV2.N3YYJDTyYPh8tv-ZFyeFZFm0Gnjv.xWbC.70JcRUVU9UelQaPsTpTWYTXBhJt84YJUw1-GNtaLNVLSjjZbVoA2fsMti5p6OBmF.7u39on2OPgvseIkSmge7Pqg63pRqdP75hp.DAEk6OkcN1jGnwP2DSbvpaSbin5lVqjfO0B-wnQgfQTCUtM5v4JmkNweLhUf9Q-x.nPKLW5SccEk9ZFXzY2-1wpT3PWm8Tix3NRscLPZub9wHzL..pl6ip8cQ9hp16UjwT4H6RMAxL0R7bl-h2pAicGAzYmuO7ntRESKUoIWA==//http%3A%2F%2Fquantum-advertising.com%2Ffr%2F\"},\"jstracker\":\"\"}", + 'adm': '{"ver":"1.1","imptrackers":["https://secure.adnxs.com/imptr?id=52311&t=2","https://euw-ice.360yield.com/imp_pixel?ic=hcUBlCANx1FabHBf6FR2gC7UO4xEyXahdZAn0-B5qL-bb3A74BJ1smyWIyW7IWcC0SOjSXzVpevTHXxTqJ.sf.Qhahyy6tSo.0j1QWfXlH8sM4-8vKWjMjw-x.IrJJNlwkQ0s1CdwcwTefcLXm5l2E-W19VhACuV7f3mgrZMNjiSw.SjJAfyPC3SIyAMRjYfj53UmjriQ46T7lhmkqxK8wHmksYCdbZc3PZESk8NWl28sxdjNvnYYCFMcJbeav.LOLabyTXfwy-1cEPbQs.IKMRZIKaqccTDPV3wOtzbNv0jQzatd3Nnv-PGFQcjQ-GW3i27W04Fws4kodpFSn-B6VwZAjzLzoyd5gBncyRnAyCplEbgHU5sZ1IyKHWjgCl3ZtRIK5vqrRD5D-xqgSnOi7-phG.CqZWDZ4bMDSfQg2ZnbvUTyGKcEl0WR59dW5izTMV4Fjizcrvr5T-t.zMbGwz.hGnmLIyhTqh.IcwW.GiDLVExlDlix5S1LXIWVsSyrQ=="],"assets":[{"id":1,"data":{"value":"ImproveDigital","type":1}},{"id":3,"data":{"value":"Test content.","type":2}},{"id":0,"title":{"text":"Sample Prebid Test Title"}}],"link":{"url":"https://euw-ice.360yield.com/click/hcUBlHOV7YhVse8RyBa0ajjyPa9Vt17e4g-1m3cRj3E67vq-RYux.SiUeAmBfNBcoOqkUc6A15AWmi4yFu5K-BdkaYjildyyk7fNLyR6hWr411kv4vrFwm5jrIBceuHS6K8oN69f.uCo8zGTdR2TbSlldwcpahQPlufZU.6VaMsu4IC53uEiUT5vb7kAw6TTlxuGBNq6zaGryiWEV2.N3YYJDTyYPh8tv-ZFyeFZFm0Gnjv.xWbC.70JcRUVU9UelQaPsTpTWYTXBhJt84YJUw1-GNtaLNVLSjjZbVoA2fsMti5p6OBmF.7u39on2OPgvseIkSmge7Pqg63pRqdP75hp.DAEk6OkcN1jGnwP2DSbvpaSbin5lVqjfO0B-wnQgfQTCUtM5v4JmkNweLhUf9Q-x.nPKLW5SccEk9ZFXzY2-1wpT3PWm8Tix3NRscLPZub9wHzL..pl6ip8cQ9hp16UjwT4H6RMAxL0R7bl-h2pAicGAzYmuO7ntRESKUoIWA==//http%3A%2F%2Fquantum-advertising.com%2Ffr%2F"},"jstracker":""}', 'impid': '33e9500b21129f', 'cid': '196108' } @@ -1049,7 +1031,7 @@ describe('Improve Digital Adapter Tests', function () { width: 728, height: 90, ttl: 300, - ad: '  ', + ad: '  ', creativeId: '510265', dealId: 320896, netRevenue: false, @@ -1060,6 +1042,12 @@ describe('Improve Digital Adapter Tests', function () { } ]; + const multiFormatExpectedBid = [ + Object.assign({}, expectedBid[0], { + ad: '  ' + }) + ]; + const expectedTwoBids = [ expectedBid[0], { @@ -1069,7 +1057,7 @@ describe('Improve Digital Adapter Tests', function () { width: 300, height: 250, ttl: 300, - ad: '  ', + ad: '  ', creativeId: '479163', dealId: 320896, netRevenue: false, @@ -1109,7 +1097,7 @@ describe('Improve Digital Adapter Tests', function () { it('should return a well-formed display bid for multi-format ad unit', function () { const bids = spec.interpretResponse(serverResponse, {bidderRequest: multiFormatBidderRequest}); - expect(bids).to.deep.equal(expectedBid); + expect(bids).to.deep.equal(multiFormatExpectedBid); }); it('should return two bids', function () { @@ -1251,16 +1239,6 @@ describe('Improve Digital Adapter Tests', function () { bids = spec.interpretResponse(videoResponse, {bidderRequest: multiFormatBidderRequest}); expect(bids[0].mediaType).to.equal(VIDEO); }); - - it('should not affect non-RAZR bids', function () { - const bids = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(bids[0].renderer).to.not.exist; - }); - - it('should detect RAZR bids', function () { - const bids = spec.interpretResponse(serverResponseRazr, {bidderRequest}); - expect(bids[0].renderer).to.exist; - }); }); describe('getUserSyncs', function () { @@ -1346,7 +1324,7 @@ describe('Improve Digital Adapter Tests', function () { it('should return iframe user sync for the adunit extend mode if iframe mode enabled', function () { // buildRequests() sets spec.syncStore vars - spec.buildRequests([simpleBidRequest, extendBidRequest]); + spec.buildRequests([simpleBidRequest, extendBidRequest], {}); const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses); expect(syncs).to.deep.equal([{ type: 'iframe', url: basicIframeSyncUrl + '&pbs=1' }]); }); @@ -1355,7 +1333,7 @@ describe('Improve Digital Adapter Tests', function () { getConfigStub = sinon.stub(config, 'getConfig'); getConfigStub.withArgs('improvedigital.extend').returns(true); // buildRequests() sets spec.syncStore vars - spec.buildRequests([simpleBidRequest]); + spec.buildRequests([simpleBidRequest], {}); const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, serverResponses); expect(syncs).to.deep.equal([{ type: 'iframe', url: basicIframeSyncUrl + '&pbs=1' }]); }); diff --git a/test/spec/modules/imuIdSystem_spec.js b/test/spec/modules/imuIdSystem_spec.js index 2934a7c213b..3650302a2ed 100644 --- a/test/spec/modules/imuIdSystem_spec.js +++ b/test/spec/modules/imuIdSystem_spec.js @@ -7,6 +7,7 @@ import { callImuidApi, getApiCallback, storageKey, + storagePpKey, cookieKey, apiUrl } from 'modules/imuIdSystem.js'; @@ -48,12 +49,17 @@ describe('imuId module', function () { describe('getId()', function () { it('should return the uid when it exists in local storages', function () { getLocalStorageStub.withArgs(storageKey).returns('testUid'); + getLocalStorageStub.withArgs(storagePpKey).returns('testPpid'); const id = imuIdSubmodule.getId(configParamTestCase); - expect(id).to.be.deep.equal({id: 'testUid'}); + expect(id).to.be.deep.equal({id: { + imuid: 'testUid', + imppid: 'testPpid' + }}); }); storageTestCasesForEmpty.forEach(testCase => it('should return the callback when it not exists in local storages', function () { getLocalStorageStub.withArgs(storageKey).returns(testCase); + getLocalStorageStub.withArgs(storagePpKey).returns(testCase); const id = imuIdSubmodule.getId(configParamTestCase); expect(id).have.all.keys('callback'); })); @@ -73,7 +79,7 @@ describe('imuId module', function () { describe('getApiUrl()', function () { it('should return default url when cid only', function () { const url = getApiUrl(5126); - expect(url).to.be.equal(`${apiUrl}?cid=5126`); + expect(url).to.be.equal(`https://sync6.im-apps.net/5126/pid`); }); it('should return param url when set url', function () { @@ -84,8 +90,14 @@ describe('imuId module', function () { describe('decode()', function () { it('should return the uid when it exists in local storages', function () { - const id = imuIdSubmodule.decode('testDecode'); - expect(id).to.be.deep.equal({imuid: 'testDecode'}); + const id = imuIdSubmodule.decode({ + imppid: 'imppid-value-imppid-value-imppid-value', + imuid: 'testDecodeImPpid' + }); + expect(id).to.be.deep.equal({ + imppid: 'imppid-value-imppid-value-imppid-value', + imuid: 'testDecodeImPpid' + }); }); it('should return the undefined when decode id is not "string"', function () { @@ -97,11 +109,13 @@ describe('imuId module', function () { describe('getLocalData()', function () { it('always have the same key', function () { getLocalStorageStub.withArgs(storageKey).returns('testid'); + getLocalStorageStub.withArgs(storagePpKey).returns('imppid-value-imppid-value-imppid-value'); getCookieStub.withArgs(cookieKey).returns('testvid'); getLocalStorageStub.withArgs(`${storageKey}_mt`).returns(new Date(utils.timestamp()).toUTCString()); const localData = getLocalData(); expect(localData).to.be.deep.equal({ id: 'testid', + ppid: 'imppid-value-imppid-value-imppid-value', vid: 'testvid', expired: false }); @@ -112,6 +126,7 @@ describe('imuId module', function () { const localData = getLocalData(); expect(localData).to.be.deep.equal({ id: undefined, + ppid: undefined, vid: undefined, expired: true }); @@ -122,6 +137,7 @@ describe('imuId module', function () { it('should return the undefined when success response', function () { const res = apiSuccessProcess({ uid: 'test', + ppid: 'imppid-value-imppid-value-imppid-value', vid: 'test' }); expect(res).to.equal(undefined); diff --git a/test/spec/modules/incrxBidAdapter_spec.js b/test/spec/modules/incrxBidAdapter_spec.js index da90cf181f3..8fb95742766 100644 --- a/test/spec/modules/incrxBidAdapter_spec.js +++ b/test/spec/modules/incrxBidAdapter_spec.js @@ -35,7 +35,7 @@ describe('IncrementX', function () { describe('buildRequests', function () { let bidderRequest = { refererInfo: { - referer: 'https://www.test.com', + page: 'https://www.test.com', reachedTop: true, isAmp: false, numIframes: 0, diff --git a/test/spec/modules/inmarBidAdapter_spec.js b/test/spec/modules/inmarBidAdapter_spec.js index 998fe20d369..d21fcbc377b 100644 --- a/test/spec/modules/inmarBidAdapter_spec.js +++ b/test/spec/modules/inmarBidAdapter_spec.js @@ -108,8 +108,10 @@ describe('Inmar adapter tests', function () { gdprApplies: true }, refererInfo: { - referer: 'https://domain.com', - numIframes: 0 + legacy: { + referer: 'https://domain.com', + numIframes: 0 + } } }); diff --git a/test/spec/modules/innityBidAdapter_spec.js b/test/spec/modules/innityBidAdapter_spec.js index d4a28ec2100..192ab4911ee 100644 --- a/test/spec/modules/innityBidAdapter_spec.js +++ b/test/spec/modules/innityBidAdapter_spec.js @@ -39,7 +39,7 @@ describe('innityAdapterTest', () => { let bidderRequest = { refererInfo: { - referer: 'https://refererExample.com' + page: 'https://refererExample.com' } }; diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 211addaf626..fc7ed1833ac 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -35,6 +35,7 @@ describe('InsticatorBidAdapter', function () { ], w: 250, h: 300, + placement: 2, }, }, bidId: '30b31c1838de1e', @@ -81,7 +82,9 @@ describe('InsticatorBidAdapter', function () { refererInfo: { numIframes: 0, reachedTop: true, - referer: 'https://example.com', + page: 'https://example.com', + domain: 'example.com', + ref: 'https://referrer.com', stack: ['https://example.com'] }, }; @@ -153,6 +156,25 @@ describe('InsticatorBidAdapter', function () { } })).to.be.false; }); + + it('should return false if video placement is not a number', () => { + expect(spec.isBidRequestValid({ + ...bidRequest, + ...{ + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + w: 250, + h: 300, + placement: 'NaN', + }, + } + } + })).to.be.false; + }); }); describe('buildRequests', function () { @@ -161,6 +183,11 @@ describe('InsticatorBidAdapter', function () { let sandbox; beforeEach(() => { + $$PREBID_GLOBAL$$.bidderSettings = { + insticator: { + storageAllowed: true + } + }; getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); getCookieStub = sinon.stub(storage, 'getCookie'); @@ -176,6 +203,7 @@ describe('InsticatorBidAdapter', function () { localStorageIsEnabledStub.restore(); getCookieStub.restore(); cookiesAreEnabledStub.restore(); + $$PREBID_GLOBAL$$.bidderSettings = {}; }); const serverRequests = spec.buildRequests([bidRequest], bidderRequest); @@ -236,7 +264,7 @@ describe('InsticatorBidAdapter', function () { expect(data.site).to.be.an('object'); expect(data.site.domain).not.to.be.empty; expect(data.site.page).not.to.be.empty; - expect(data.site.ref).to.equal(bidderRequest.refererInfo.referer); + expect(data.site.ref).to.equal(bidderRequest.refererInfo.ref); expect(data.device).to.be.an('object'); expect(data.device.w).to.equal(window.innerWidth); expect(data.device.h).to.equal(window.innerHeight); @@ -248,7 +276,6 @@ describe('InsticatorBidAdapter', function () { expect(data.regs.ext.gdpr).to.equal(1); expect(data.regs.ext.gdprConsentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.user).to.be.an('object'); - expect(data.user.id).to.equal(USER_ID_DUMMY_VALUE); expect(data.user).to.have.property('yob'); expect(data.user.yob).to.equal(1984); expect(data.user).to.have.property('gender'); @@ -274,7 +301,7 @@ describe('InsticatorBidAdapter', function () { banner: { format: [ { w: 300, h: 250 }, - { w: 300, h: 600 }, + { w: 300, h: 600 } ] }, video: { @@ -284,6 +311,7 @@ describe('InsticatorBidAdapter', function () { ], h: 300, w: 250, + placement: 2, }, ext: { gpid: bidRequest.ortb2Imp.ext.gpid, diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 39d6271606a..2b98a9f8960 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -14,7 +14,9 @@ describe('invibesBidAdapter:', function () { bidder: BIDDER_CODE, bidderRequestId: 'r1', params: { - placementId: PLACEMENT_ID + placementId: PLACEMENT_ID, + disableUserSyncs: false + }, adUnitCode: 'test-div1', auctionId: 'a1', @@ -29,7 +31,8 @@ describe('invibesBidAdapter:', function () { bidder: BIDDER_CODE, bidderRequestId: 'r2', params: { - placementId: 'abcde' + placementId: 'abcde', + disableUserSyncs: false }, adUnitCode: 'test-div2', auctionId: 'a2', @@ -107,6 +110,11 @@ describe('invibesBidAdapter:', function () { beforeEach(function () { resetInvibes(); + $$PREBID_GLOBAL$$.bidderSettings = { + invibes: { + storageAllowed: true + } + }; document.cookie = ''; this.cStub1 = sinon.stub(console, 'info'); }); @@ -165,6 +173,16 @@ describe('invibesBidAdapter:', function () { }); describe('buildRequests', function () { + it('sends preventPageViewEvent as false on first call', function () { + let request = spec.buildRequests(bidRequests); + expect(request.data.preventPageViewEvent).to.be.false; + }); + + it('sends preventPageViewEvent as true on 2nd call', function () { + let request = spec.buildRequests(bidRequests); + expect(request.data.preventPageViewEvent).to.be.true; + }); + it('sends bid request to ENDPOINT via GET', function () { const request = spec.buildRequests(bidRequests); expect(request.url).to.equal(ENDPOINT); @@ -1195,7 +1213,15 @@ describe('invibesBidAdapter:', function () { }); describe('getUserSyncs', function () { + it('returns undefined if disableUserSyncs not passed as bid request param ', function () { + spec.buildRequests(bidRequestsWithUserId); + let response = spec.getUserSyncs({iframeEnabled: true}); + expect(response).to.equal(undefined); + }); + it('returns an iframe if enabled', function () { + spec.buildRequests(bidRequests); + let response = spec.getUserSyncs({iframeEnabled: true}); expect(response.type).to.equal('iframe'); expect(response.url).to.include(SYNC_ENDPOINT); @@ -1203,6 +1229,8 @@ describe('invibesBidAdapter:', function () { it('returns an iframe with params if enabled', function () { top.window.invibes.optIn = 1; + spec.buildRequests(bidRequests); + let response = spec.getUserSyncs({iframeEnabled: true}); expect(response.type).to.equal('iframe'); expect(response.url).to.include(SYNC_ENDPOINT); @@ -1211,6 +1239,8 @@ describe('invibesBidAdapter:', function () { it('returns an iframe with params including if enabled', function () { top.window.invibes.optIn = 1; + spec.buildRequests(bidRequests); + global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":' + Date.now() + ',"hc":0}'; SetBidderAccess(); @@ -1222,7 +1252,9 @@ describe('invibesBidAdapter:', function () { }); it('returns an iframe with params including if enabled read from LocalStorage', function () { + spec.buildRequests(bidRequests); top.window.invibes.optIn = 1; + localStorage.ivbsdid = 'dvdjkams6nkq'; SetBidderAccess(); @@ -1234,6 +1266,8 @@ describe('invibesBidAdapter:', function () { }); it('returns undefined if iframe not enabled ', function () { + spec.buildRequests(bidRequests); + let response = spec.getUserSyncs({iframeEnabled: false}); expect(response).to.equal(undefined); }); diff --git a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js index e13b16661b0..d97b0925713 100644 --- a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js +++ b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js @@ -202,7 +202,9 @@ describe('Invisibly Analytics Adapter test suite', function () { events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); - sinon.assert.callCount(invisiblyAdapter.track, 5); + + // 5 Invisibly events + 1 Clean.io event + sinon.assert.callCount(invisiblyAdapter.track, 6); }); it('should not catch events triggered without invisibly account config', function () { @@ -380,7 +382,9 @@ describe('Invisibly Analytics Adapter test suite', function () { expect(invisiblyEvents.event_data.pageViewId).to.exist; expect(invisiblyEvents.event_data.ver).to.equal(1); expect(invisiblyEvents.event_type).to.equal('PREBID_bidWon'); - sinon.assert.callCount(invisiblyAdapter.track, 1); + + // 1 Invisibly event + 1 Clean.io event + sinon.assert.callCount(invisiblyAdapter.track, 2); }); // spec for bidder done event @@ -551,7 +555,8 @@ describe('Invisibly Analytics Adapter test suite', function () { events.emit(constants.EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED); - sinon.assert.callCount(invisiblyAdapter.track, 13); + // 13 Invisibly events + 1 Clean.io event + sinon.assert.callCount(invisiblyAdapter.track, 14); }); }); diff --git a/test/spec/modules/ipromBidAdapter_spec.js b/test/spec/modules/ipromBidAdapter_spec.js index a3310a33cc2..bb2f364bece 100644 --- a/test/spec/modules/ipromBidAdapter_spec.js +++ b/test/spec/modules/ipromBidAdapter_spec.js @@ -29,13 +29,15 @@ describe('iPROM Adapter', function () { bidderRequest = { timeout: 3000, refererInfo: { - referer: 'https://adserver.si/index.html', - reachedTop: true, - numIframes: 1, - stack: [ - 'https://adserver.si/index.html', - 'https://adserver.si/iframe1.html', - ] + legacy: { + referer: 'https://adserver.si/index.html', + reachedTop: true, + numIframes: 1, + stack: [ + 'https://adserver.si/index.html', + 'https://adserver.si/iframe1.html', + ] + } } } }); diff --git a/test/spec/modules/iqmBidAdapter_spec.js b/test/spec/modules/iqmBidAdapter_spec.js index 27693937330..2f8b5811b2f 100644 --- a/test/spec/modules/iqmBidAdapter_spec.js +++ b/test/spec/modules/iqmBidAdapter_spec.js @@ -101,7 +101,7 @@ describe('iqmAdapter', function () { bidfloor: 0.5}, crumbs: { pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, - fpd: {'context': {'pbAdSlot': '/19968336/header-bid-tag-0'}}, + ortb2Imp: {ext: {data: {'pbadslot': '/19968336/header-bid-tag-0'}}}, mediaTypes: { banner: { sizes: [[300, 250]]}}, @@ -116,7 +116,41 @@ describe('iqmAdapter', function () { bidderRequestsCount: 1, bidderWinsCount: 0}]; - let bidderRequest = {bidderCode: 'iqm', auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', bidderRequestId: '13c05d264c7ffe', bids: [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5}, crumbs: {pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, fpd: {context: {pbAdSlot: '/19968336/header-bid-tag-0'}}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: '/19968336/header-bid-tag-0', transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', sizes: [[300, 250]], bidId: '266d810da21904', bidderRequestId: '13c05d264c7ffe', auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], auctionStart: 1615205942159, timeout: 7000, refererInfo: {referer: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://test.localhost:9999/integrationExamples/gpt/hello_world.html'], canonicalUrl: null}, start: 1615205942162}; + let bidderRequest = { + bidderCode: 'iqm', + auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', + bidderRequestId: '13c05d264c7ffe', + bids: [{ + bidder: 'iqm', + params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5}, + crumbs: {pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, + ortb2Imp: {ext: {data: {'pbadslot': '/19968336/header-bid-tag-0'}}}, + mediaTypes: {banner: {sizes: [[300, 250]]}}, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', + sizes: [[300, 250]], + bidId: '266d810da21904', + bidderRequestId: '13c05d264c7ffe', + auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }], + auctionStart: 1615205942159, + timeout: 7000, + refererInfo: { + page: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', + domain: 'test.localhost.com:9999', + ref: null, + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['http://test.localhost:9999/integrationExamples/gpt/hello_world.html'], + canonicalUrl: null + }, + start: 1615205942162 + }; it('should parse out sizes', function () { let temp = []; @@ -141,8 +175,80 @@ describe('iqmAdapter', function () { expect(request[0].method).to.equal('POST'); }); it('should attach valid video params to the tag', function () { - let validBidRequests_video = [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5, video: {placement: 2, mimes: ['video/mp4'], protocols: [2, 5], skipppable: true, playback_method: ['auto_play_sound_off']}}, crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, fpd: {context: {pbAdSlot: 'video1'}}, mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, adUnitCode: 'video1', transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', sizes: [[640, 480]], bidId: '28bfb7e2d12897', bidderRequestId: '16e1ce8481bc6d', auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}]; - let bidderRequest_video = {bidderCode: 'iqm', auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', bidderRequestId: '16e1ce8481bc6d', bids: [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5, video: {placement: 2, mimes: ['video/mp4'], protocols: [2, 5], skipppable: true, playback_method: ['auto_play_sound_off']}}, crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, fpd: {context: {pbAdSlot: 'video1'}}, mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, adUnitCode: 'video1', transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', sizes: [[640, 480]], bidId: '28bfb7e2d12897', bidderRequestId: '16e1ce8481bc6d', auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], auctionStart: 1615271191985, timeout: 3000, refererInfo: {referer: 'http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html'], canonicalUrl: null}, start: 1615271191988}; + let validBidRequests_video = [{ + bidder: 'iqm', + params: { + publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', + placementId: 23451, + bidfloor: 0.5, + video: { + placement: 2, + mimes: ['video/mp4'], + protocols: [2, 5], + skipppable: true, + playback_method: ['auto_play_sound_off'] + } + }, + crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, + ortb2Imp: {ext: {data: {'pbadslot': 'video1'}}}, + mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, + adUnitCode: 'video1', + transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', + sizes: [[640, 480]], + bidId: '28bfb7e2d12897', + bidderRequestId: '16e1ce8481bc6d', + auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }]; + let bidderRequest_video = { + bidderCode: 'iqm', + auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', + bidderRequestId: '16e1ce8481bc6d', + bids: [{ + bidder: 'iqm', + params: { + publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', + placementId: 23451, + bidfloor: 0.5, + video: { + placement: 2, + mimes: ['video/mp4'], + protocols: [2, 5], + skipppable: true, + playback_method: ['auto_play_sound_off'] + } + }, + crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, + fpd: {context: {pbAdSlot: 'video1'}}, + mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, + adUnitCode: 'video1', + transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', + sizes: [[640, 480]], + bidId: '28bfb7e2d12897', + bidderRequestId: '16e1ce8481bc6d', + auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }], + auctionStart: 1615271191985, + timeout: 3000, + refererInfo: { + page: 'http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html', + domain: 'test.localhost.com:9999', + ref: null, + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html'], + canonicalUrl: null + }, + start: 1615271191988 + }; const request = spec.buildRequests(validBidRequests_video, bidderRequest_video); const payload = request[0].data; expect(payload.imp.id).to.exist; @@ -161,11 +267,13 @@ describe('iqmAdapter', function () { }); it('should add referer info to payload', function () { + // TODO: this is wrong on multiple levels + // The payload contains everything in `bidderRequest`; that is sometimes not even serializable + // this should not be testing the validity of internal Prebid structures const request = spec.buildRequests(validBidRequests, bidderRequest); const payload = request[0].data; expect(payload.bidderRequest.refererInfo).to.exist; - expect(payload.bidderRequest.refererInfo).to.deep.equal({referer: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://test.localhost:9999/integrationExamples/gpt/hello_world.html'], canonicalUrl: null}); }); }) @@ -179,7 +287,7 @@ describe('iqmAdapter', function () { bidfloor: 0.5}, crumbs: { pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, - fpd: {'context': {'pbAdSlot': '/19968336/header-bid-tag-0'}}, + ortb2Imp: {ext: {data: {'pbadslot': '/19968336/header-bid-tag-0'}}}, mediaTypes: { banner: { sizes: [[300, 250]]}}, @@ -193,7 +301,41 @@ describe('iqmAdapter', function () { bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}]; - let bidderRequest = {bidderCode: 'iqm', auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', bidderRequestId: '13c05d264c7ffe', bids: [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5}, crumbs: {pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, fpd: {context: {pbAdSlot: '/19968336/header-bid-tag-0'}}, mediaTypes: {banner: {sizes: [[300, 250]]}}, adUnitCode: '/19968336/header-bid-tag-0', transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', sizes: [[300, 250]], bidId: '266d810da21904', bidderRequestId: '13c05d264c7ffe', auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], auctionStart: 1615205942159, timeout: 7000, refererInfo: {referer: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://test.localhost:9999/integrationExamples/gpt/hello_world.html'], canonicalUrl: null}, start: 1615205942162}; + let bidderRequest = { + bidderCode: 'iqm', + auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', + bidderRequestId: '13c05d264c7ffe', + bids: [{ + bidder: 'iqm', + params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5}, + crumbs: {pubcid: 'a0f51f64-6d86-41d0-abaf-7ece71404d94'}, + ortb2Imp: {ext: {data: {'pbadslot': '/19968336/header-bid-tag-0'}}}, + mediaTypes: {banner: {sizes: [[300, 250]]}}, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: '56fe8d92-ff6e-4c34-90ad-2f743cd0eae8', + sizes: [[300, 250]], + bidId: '266d810da21904', + bidderRequestId: '13c05d264c7ffe', + auctionId: '565ab569-ab95-40d6-8b42-b9707a92062f', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }], + auctionStart: 1615205942159, + timeout: 7000, + refererInfo: { + page: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', + domain: 'test.localhost.com:9999', + ref: null, + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['http://test.localhost:9999/integrationExamples/gpt/hello_world.html'], + canonicalUrl: null + }, + start: 1615205942162 + }; let response = { id: '5bdbab92aae961cfbdf7465d', @@ -213,7 +355,52 @@ describe('iqmAdapter', function () { let validBidRequests_temp_video = [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5, video: {placement: 2, mimes: ['video/mp4'], protocols: [2, 5], skipppable: true, playback_method: ['auto_play_sound_off']}}, crumbs: {pubcid: 'cd86c3ff-d630-40e6-83ab-420e9e800594'}, fpd: {context: {pbAdSlot: 'video1'}}, mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, adUnitCode: 'video1', transactionId: '8335b266-7a41-45f9-86a2-92fdc7cf0cd9', sizes: [[640, 480]], bidId: '26274beff25455', bidderRequestId: '17c5d8c3168761', auctionId: '2c592dcf-7dfc-4823-8203-dd1ebab77fe0', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}]; - let bidderRequest_video = {bidderCode: 'iqm', auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', bidderRequestId: '16e1ce8481bc6d', bids: [{bidder: 'iqm', params: {publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', placementId: 23451, bidfloor: 0.5, video: {placement: 2, mimes: ['video/mp4'], protocols: [2, 5], skipppable: true, playback_method: ['auto_play_sound_off']}}, crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, fpd: {context: {pbAdSlot: 'video1'}}, mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, adUnitCode: 'video1', transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', sizes: [[640, 480]], bidId: '28bfb7e2d12897', bidderRequestId: '16e1ce8481bc6d', auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, bidderWinsCount: 0}], auctionStart: 1615271191985, timeout: 3000, refererInfo: {referer: 'http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html', reachedTop: true, isAmp: false, numIframes: 0, stack: ['http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html'], canonicalUrl: null}, start: 1615271191988}; + let bidderRequest_video = { + bidderCode: 'iqm', + auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', + bidderRequestId: '16e1ce8481bc6d', + bids: [{ + bidder: 'iqm', + params: { + publisherId: 'df5fd732-c5f3-11e7-abc4-cec278b6b50a', + placementId: 23451, + bidfloor: 0.5, + video: { + placement: 2, + mimes: ['video/mp4'], + protocols: [2, 5], + skipppable: true, + playback_method: ['auto_play_sound_off'] + } + }, + crumbs: {pubcid: '09b8f065-9d1b-4a36-bd0c-ea22e2dad807'}, + ortb2Imp: {ext: {data: {'pbadslot': 'video1'}}}, + mediaTypes: {video: {playerSize: [[640, 480]], context: 'instream'}}, + adUnitCode: 'video1', + transactionId: '86795c66-acf9-4dd5-998f-6d5362aaa541', + sizes: [[640, 480]], + bidId: '28bfb7e2d12897', + bidderRequestId: '16e1ce8481bc6d', + auctionId: '3140a2ec-d567-4db0-9bbb-eb6fa20ccb71', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }], + auctionStart: 1615271191985, + timeout: 3000, + refererInfo: { + page: 'http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html', + domain: 'test.localhost.com:9999', + ref: '', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: ['http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html'], + canonicalUrl: null + }, + start: 1615271191988 + }; it('handles non-banner media responses', function () { let response = {id: '2341234', seatbid: [{bid: [{id: 'bid-2341234-1', impid: '1', price: 9, nurl: 'https://frontend.stage.iqm.com/static/vast-01.xml', adm: 'http://cdn.iqm.com/pbd?raw=312730_203cf73dc83fb_2824348636878_pbd', adomain: ['app1.stage.iqm.com'], cid: '168900', crid: 'cr-304503', attr: []}]}], bidid: '2341234'}; diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 9fe3bd8fb22..60ea2df0510 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -4,10 +4,10 @@ import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec, storage, ERROR_CODES } from '../../../modules/ixBidAdapter.js'; import { createEidsArray } from 'modules/userId/eids.js'; -import { deepAccess } from '../../../src/utils.js'; +import { deepAccess, deepClone } from '../../../src/utils.js'; describe('IndexexchangeAdapter', function () { - const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; + const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/openrtb/pbjs'; const VIDEO_ENDPOINT_VERSION = 8.1; const BANNER_ENDPOINT_VERSION = 7.2; @@ -120,6 +120,11 @@ describe('IndexexchangeAdapter', function () { playerSize: [[400, 100]] } }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47230' + } + }, adUnitCode: 'div-gpt-ad-1460505748562-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', @@ -142,6 +147,11 @@ describe('IndexexchangeAdapter', function () { sizes: [[300, 250]] } }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' + } + }, adUnitCode: 'div-gpt-ad-1460505748561-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', bidId: '1a2b3c4d', @@ -161,7 +171,13 @@ describe('IndexexchangeAdapter', function () { sizes: [[300, 250], [300, 600]], mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]] + sizes: [[300, 250], [300, 600]], + pos: 0 + } + }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' } }, adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -184,6 +200,11 @@ describe('IndexexchangeAdapter', function () { sizes: [[300, 250], [300, 600]] } }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47229' + } + }, adUnitCode: 'div-gpt-ad-1460505748561-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', bidId: '1a2b3c4d', @@ -213,6 +234,11 @@ describe('IndexexchangeAdapter', function () { protocols: [2] } }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47230' + } + }, adUnitCode: 'div-gpt-ad-1460505748562-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', @@ -246,6 +272,11 @@ describe('IndexexchangeAdapter', function () { playerSize: [[400, 100], [200, 400]] } }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47230' + } + }, adUnitCode: 'div-gpt-ad-1460505748562-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', @@ -259,6 +290,7 @@ describe('IndexexchangeAdapter', function () { { bidder: 'ix', params: { + tagId: '123', siteId: '123', size: [300, 250], }, @@ -271,6 +303,14 @@ describe('IndexexchangeAdapter', function () { sizes: [[300, 250], [300, 600], [400, 500]] } }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47230', + data: { + pbadslot: 'div-gpt-ad-1460505748562-0' + } + } + }, adUnitCode: 'div-gpt-ad-1460505748562-0', transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', @@ -284,6 +324,7 @@ describe('IndexexchangeAdapter', function () { { bidder: 'ix', params: { + tagId: '123', siteId: '456', video: { skippable: false, @@ -306,6 +347,14 @@ describe('IndexexchangeAdapter', function () { sizes: [[300, 250], [300, 600]] } }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47230', + data: { + pbadslot: 'div-gpt-ad-1460505748562-0' + } + } + }, adUnitCode: 'div-gpt-ad-1460505748562-0', transactionId: '273f49a8-7549-4218-a23c-e7ba59b47230', bidId: '1a2b3c4e', @@ -315,6 +364,99 @@ describe('IndexexchangeAdapter', function () { } ]; + const DEFAULT_NATIVE_VALID_BID = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250] + }, + sizes: [[300, 250], [300, 600]], + mediaTypes: { + native: { + icon: { + required: false + }, + title: { + len: 25, + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + video: { + required: false, + mimes: ['video/mp4', 'video/webm'], + minduration: 0, + maxduration: 120, + protocols: [2, 3, 5, 6] + }, + sponsoredBy: { + required: true + } + } + }, + nativeOrtbRequest: { + assets: [{id: 0, required: 0, img: {type: 1}}, {id: 1, required: 1, title: {len: 140}}, {id: 2, required: 1, data: {type: 2}}, {id: 3, required: 1, img: {type: 3}}, {id: 4, required: false, video: {mimes: ['video/mp4', 'video/webm'], minduration: 0, maxduration: 120, protocols: [2, 3, 5, 6]}}] + }, + adUnitCode: 'div-gpt-ad-1460505748563-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47231', + bidId: '1a2b3c4f', + bidderRequestId: '11a22b33c44f', + auctionId: '1aa2bb3cc4df', + schain: SAMPLE_SCHAIN + } + ]; + + const DEFAULT_MULTIFORMAT_NATIVE_VALID_BID = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250], + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600], [400, 500]] + }, + native: { + title: { + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + }, + icon: { + required: false + } + } + }, + nativeOrtbRequest: { + assets: [{id: 0, required: 0, img: {type: 1}}, {id: 1, required: 1, title: {len: 140}}, {id: 2, required: 1, data: {type: 2}}, {id: 3, required: 1, img: {type: 3}}, {id: 4, required: false, video: {mimes: ['video/mp4', 'video/webm'], minduration: 0, maxduration: 120, protocols: [2, 3, 5, 6]}}] + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de', + schain: SAMPLE_SCHAIN + } + ]; + + const DEFAULT_NATIVE_IMP = { + request: '{"assets":[{"id":0,"required":0,"img":{"type":1}},{"id":1,"required":1,"title":{"len":140}},{"id":2,"required":1,"data":{"type":2}},{"id":3,"required":1,"img":{"type":3}},{"id":4,"required":false,"video":{"mimes":["video/mp4","video/webm"],"minduration":0,"maxduration":120,"protocols":[2,3,5,6]}}],"eventtrackers":[{"event":1,"methods":[1,2]}],"privacy":1}', + ver: '1.2' + } + const DEFAULT_BANNER_BID_RESPONSE = { cur: 'USD', id: '11a22b33c44d', @@ -437,6 +579,34 @@ describe('IndexexchangeAdapter', function () { ] }; + const DEFAULT_NATIVE_BID_RESPONSE = { + cur: 'USD', + id: '11a22b33c44d', + seatbid: [ + { + bid: [ + { + crid: '12345', + adomain: ['www.abc.com'], + adid: '14851455', + impid: '1a2b3c4d', + cid: '3051266', + price: 100, + id: '1', + ext: { + dspid: 50, + pricelevel: '_100', + advbrandid: 303325, + advbrand: 'OECTA' + }, + adm: '{"native":{"ver":"1.2","assets":[{"id":0,"img":{"url":"https://cdn.liftoff.io/customers/1209/creatives/2501-icon-250x250.png","w":250,"h":250}},{"id":1,"img":{"url":"https://cdn.liftoff.io/customers/5a9cab9cc6/image/lambda_png/a0355879b06c09b09232.png","w":1200,"h":627}},{"id":2,"data":{"value":"autodoc.co.uk"}},{"id":3,"data":{"value":"Les pièces automobiles dont vous avez besoin, toujours sous la main."}},{"id":4,"title":{"text":"Autodoc"}},{"id":5,"video":{"vasttag":"blah"}}],"link":{"url":"https://play.google.com/store/apps/details?id=de.autodoc.gmbh","clicktrackers":["https://click.liftoff.io/v1/campaign_click/blah"]},"eventtrackers":[{"event":1,"method":1,"url":"https://impression-europe.liftoff.io/index/impression"},{"event":1,"method":1,"url":"https://a701.casalemedia.com/impression/v1"}],"privacy":"https://privacy.link.com"}}' + } + ], + seat: '3970' + } + ] + }; + const DEFAULT_OPTION = { gdprConsent: { gdprApplies: true, @@ -444,7 +614,7 @@ describe('IndexexchangeAdapter', function () { vendorData: {} }, refererInfo: { - referer: 'https://www.prebid.org', + page: 'https://www.prebid.org', canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' } }; @@ -551,23 +721,9 @@ describe('IndexexchangeAdapter', function () { ]; const DEFAULT_USERID_BID_DATA = { - lotamePanoramaId: 'bd738d136bdaa841117fe9b331bb4', - flocId: { id: '1234', version: 'chrome.1.2' } + lotamePanoramaId: 'bd738d136bdaa841117fe9b331bb4' }; - const DEFAULT_FLOC_USERID_PAYLOAD = [ - { - source: 'chrome.com', - uids: [{ - id: DEFAULT_USERID_BID_DATA.flocId.id, - ext: { - rtiPartner: 'flocId', - ver: DEFAULT_USERID_BID_DATA.flocId.version - } - }] - } - ]; - describe('inherited functions', function () { it('should exists and is a function', function () { const adapter = newBidder(spec); @@ -580,26 +736,131 @@ describe('IndexexchangeAdapter', function () { const syncOptions = { 'iframeEnabled': true } - let userSync = spec.getUserSyncs(syncOptions); + let userSync = spec.getUserSyncs(syncOptions, []); expect(userSync[0].type).to.equal('iframe'); const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; expect(userSync[0].url).to.equal(USER_SYNC_URL); }); - it('When iframeEnabled is false, no userSync should be returned', function () { + it('When iframeEnabled = false, default to img', function () { + const syncOptions = { + 'iframeEnabled': false, + } + let userSync = spec.getUserSyncs(syncOptions, []); + expect(userSync[0].type).to.equal('image'); + const USER_SYNC_URL = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=1&i=0&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + expect(userSync[0].url).to.equal(USER_SYNC_URL); + }); + + it('UserSync test : check type = pixel, check usermatch URL, no exchange data, only drop 1', function () { + const syncOptions = { + 'pixelEnabled': true + } + config.setConfig({ + userSync: { + pixelEnabled: true, + syncsPerBidder: 3 + } + }) + let userSync = spec.getUserSyncs(syncOptions, []); + expect(userSync[0].type).to.equal('image'); + const USER_SYNC_URL = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=1&i=0&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + expect(userSync[0].url).to.equal(USER_SYNC_URL); + }); + + it('UserSync test : check type = pixel, check usermatch URL with override set to 0', function () { + const syncOptions = { + 'pixelEnabled': true + } + config.setConfig({ + userSync: { + pixelEnabled: true, + syncsPerBidder: 3 + } + }); + let userSync = spec.getUserSyncs(syncOptions, [{'body': {'ext': {'publishersyncsperbidderoverride': 0}}}]); + expect(userSync.length).to.equal(0); + }); + + it('UserSync test : check type = pixel, check usermatch URL with override set', function () { const syncOptions = { - 'iframeEnabled': false + 'pixelEnabled': true } - let userSync = spec.getUserSyncs(syncOptions); - expect(userSync).to.be.an('array').that.is.empty; + config.setConfig({ + userSync: { + pixelEnabled: true, + syncsPerBidder: 3 + } + }); + let userSync = spec.getUserSyncs(syncOptions, [{'body': {'ext': {'publishersyncsperbidderoverride': 2}}}]); + expect(userSync[0].type).to.equal('image'); + const USER_SYNC_URL_0 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=2&i=0&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + const USER_SYNC_URL_1 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=2&i=1&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + expect(userSync[0].url).to.equal(USER_SYNC_URL_0); + expect(userSync[1].url).to.equal(USER_SYNC_URL_1); + expect(userSync.length).to.equal(2); + }); + + it('UserSync test : check type = pixel, check usermatch URL with override greater than publisher syncs per bidder , use syncsperbidder', function () { + const syncOptions = { + 'pixelEnabled': true + } + config.setConfig({ + userSync: { + pixelEnabled: true, + syncsPerBidder: 3 + } + }); + let userSync = spec.getUserSyncs(syncOptions, [{'body': {'ext': {'publishersyncsperbidderoverride': 4}}}]); + expect(userSync[0].type).to.equal('image'); + const USER_SYNC_URL_0 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=3&i=0&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + const USER_SYNC_URL_1 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=3&i=1&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + const USER_SYNC_URL_2 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=3&i=2&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + expect(userSync[0].url).to.equal(USER_SYNC_URL_0); + expect(userSync[1].url).to.equal(USER_SYNC_URL_1); + expect(userSync[2].url).to.equal(USER_SYNC_URL_2); + expect(userSync.length).to.equal(3); + }); + + it('UserSync test : check type = pixel, syncsPerBidder = 0, still use override', function () { + const syncOptions = { + 'pixelEnabled': true + } + config.setConfig({ + userSync: { + pixelEnabled: true, + syncsPerBidder: 0 + } + }); + let userSync = spec.getUserSyncs(syncOptions, [{'body': {'ext': {'publishersyncsperbidderoverride': 2}}}]); + expect(userSync[0].type).to.equal('image'); + const USER_SYNC_URL_0 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=2&i=0&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + const USER_SYNC_URL_1 = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid&site_id=123&p=2&i=1&gdpr=1&gdpr_consent=3huaa11=qu3198ae&us_privacy='; + expect(userSync[0].url).to.equal(USER_SYNC_URL_0); + expect(userSync[1].url).to.equal(USER_SYNC_URL_1); + expect(userSync.length).to.equal(2); }); }); describe('isBidRequestValid', function () { - it('should return false if outstream player size is less than 300x250 and IX renderer is preferred', function () { + it('should return false if outstream player size is less than 144x144 and IX renderer is preferred', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; - bid.mediaTypes.video.playerSize = [[300, 249]]; + bid.mediaTypes.video.w = [[300, 143]]; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.mediaTypes.video.w = [[143, 300]]; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if outstream video w & h is less than 144x144 and IX renderer is preferred', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'outstream'; + bid.mediaTypes.video.playerSize = [[300, 250]]; + bid.mediaTypes.video.w = 300; + bid.mediaTypes.video.h = 142; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.mediaTypes.video.h = 300; + bid.mediaTypes.video.w = 142; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -614,9 +875,10 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return true when required params found for a banner or video ad', function () { + it('should return true when required params found for a banner, video or native ad', function () { expect(spec.isBidRequestValid(DEFAULT_BANNER_VALID_BID[0])).to.equal(true); expect(spec.isBidRequestValid(DEFAULT_VIDEO_VALID_BID[0])).to.equal(true); + expect(spec.isBidRequestValid(DEFAULT_NATIVE_VALID_BID[0])).to.equal(true); }); it('should return true when optional bidFloor params found for an ad', function () { @@ -705,11 +967,11 @@ describe('IndexexchangeAdapter', function () { }); it('should return false when mediaType is native', function () { - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const bid = utils.deepClone(DEFAULT_NATIVE_VALID_BID[0]); delete bid.params.mediaTypes; bid.mediaType = 'native'; bid.sizes = [[300, 250]]; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true when mediaType is missing and has sizes', function () { @@ -745,6 +1007,11 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + it('shoult return true for native bid when there are multiple mediaTypes (banner, native)', function () { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_NATIVE_VALID_BID[0]); + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('should return false when there is only bidFloor', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; @@ -807,6 +1074,15 @@ describe('IndexexchangeAdapter', function () { delete bid.params.video.protocols; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + + it('should fail if native openRTB object contains no valid assets', function () { + let bid = utils.deepClone(DEFAULT_NATIVE_VALID_BID[0]); + bid.nativeOrtbRequest = {} + expect(spec.isBidRequestValid(bid)).to.be.false; + + bid.nativeOrtbRequest = {assets: []} + expect(spec.isBidRequestValid(bid)).to.be.false; + }); }); describe('Roundel alias adapter', function () { @@ -1034,76 +1310,6 @@ describe('IndexexchangeAdapter', function () { expect(payload.user.eids).to.have.deep.members(DEFAULT_USERID_PAYLOAD); }); - it('IX adapter reads floc id from prebid userId and adds it to eids when there is not other eids', function () { - const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); - cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_BID_DATA); - const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); - - expect(payload.user.eids).to.have.lengthOf(1); - expect(payload.user.eids).to.deep.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); - }); - - it('IX adapter reads floc id from prebid userId and appends it to eids', function () { - const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); - cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); - cloneValidBid[0].userId = utils.deepClone(DEFAULT_USERID_BID_DATA); - const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); - - expect(payload.user.eids).to.have.lengthOf(7); - expect(payload.user.eids).to.deep.include.members(DEFAULT_USERID_PAYLOAD); - expect(payload.user.eids).to.deep.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); - }); - - it('IX adapter reads empty floc obj from prebid userId it, floc is not added to eids', function () { - const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); - cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); - cloneValidBid[0].userId = { 'flocId': {} } - const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); - - expect(payload.user.eids).to.have.lengthOf(6); - expect(payload.user.eids).to.deep.include.members(DEFAULT_USERID_PAYLOAD); - expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); - }); - - it('IX adapter reads floc obj from prebid userId it version is missing, floc is not added to eids', function () { - const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); - cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); - cloneValidBid[0].userId = { 'flocId': { 'id': 'abcd' } } - const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); - - expect(payload.user.eids).to.have.lengthOf(6); - expect(payload.user.eids).to.deep.include.members(DEFAULT_USERID_PAYLOAD); - expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); - }); - - it('IX adapter reads floc obj from prebid userId it ID is missing, floc is not added to eids', function () { - const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); - cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); - cloneValidBid[0].userId = { 'flocId': { 'version': 'chrome.a.b.c' } } - const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); - - expect(payload.user.eids).to.have.lengthOf(6); - expect(payload.user.eids).to.deep.include.members(DEFAULT_USERID_PAYLOAD); - expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); - }); - - it('IX adapter reads floc id with empty id from prebid userId and it does not added to eids', function () { - const cloneValidBid = utils.deepClone(DEFAULT_BANNER_VALID_BID); - cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); - cloneValidBid[0].userId = { flocID: { id: '', ver: 'chrome.1.2.3' } }; - const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; - const payload = JSON.parse(request.data.r); - - expect(payload.user.eids).to.have.lengthOf(6); - expect(payload.user.eids).to.deep.include.members(DEFAULT_USERID_PAYLOAD); - expect(payload.user.eids).should.not.include(DEFAULT_FLOC_USERID_PAYLOAD[0]); - }); - it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { validIdentityResponse = { AdserverOrgIp: { @@ -1307,100 +1513,65 @@ describe('IndexexchangeAdapter', function () { }); describe('First party data', function () { - afterEach(function () { - config.setConfig({ - ortb2: {} - }); - }); - it('should not set ixdiag.fpd value if not defined', function () { - config.setConfig({ - ortb2: {} - }); - - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {ortb2: {}})[0]; const r = JSON.parse(request.data.r); expect(r.ext.ixdiag.fpd).to.be.undefined; }); - it('should set ixdiag.fpd value if it exists using fpd', function () { - config.setConfig({ - fpd: { - site: { + it('should set ixdiag.fpd value if it exists using ortb2', function () { + const ortb2 = { + site: { + ext: { data: { pageType: 'article' } } } - }); + }; - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {ortb2})[0]; const r = JSON.parse(request.data.r); expect(r.ext.ixdiag.fpd).to.exist; }); - it('should set ixdiag.fpd value if it exists using ortb2', function () { + it('should set ixdiag.tmax value if it exists using tmax', function () { config.setConfig({ - ortb2: { - site: { - ext: { - data: { - pageType: 'article' - } - } - } - } + bidderTimeout: 250 }); const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; const r = JSON.parse(request.data.r); - expect(r.ext.ixdiag.fpd).to.exist; + expect(r.ext.ixdiag.tmax).to.equal(250); }); - it('should not send information that is not part of openRTB spec v2.5 using fpd', function () { + it('should not set ixdiag.tmax value if bidderTimeout is undefined', function () { config.setConfig({ - fpd: { - site: { - keywords: 'power tools, drills', - search: 'drill', - testProperty: 'test_string' - }, - user: { - keywords: ['a'], - testProperty: 'test_string' - } - } - }); - + bidderTimeout: null + }) const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; const r = JSON.parse(request.data.r); - expect(r.site.keywords).to.exist; - expect(r.site.search).to.exist; - expect(r.site.testProperty).to.be.undefined; - expect(r.user.keywords).to.exist; - expect(r.user.testProperty).to.be.undefined; + expect(r.ext.ixdiag.tmax).to.be.undefined }); it('should not send information that is not part of openRTB spec v2.5 using ortb2', function () { - config.setConfig({ - ortb2: { - site: { - keywords: 'power tools, drills', - search: 'drill', - testProperty: 'test_string' - }, - user: { - keywords: ['a'], - testProperty: 'test_string' - } + const ortb2 = { + site: { + keywords: 'power tools, drills', + search: 'drill', + testProperty: 'test_string' + }, + user: { + keywords: ['a'], + testProperty: 'test_string' } - }); + }; - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {ortb2})[0]; const r = JSON.parse(request.data.r); expect(r.site.keywords).to.exist; @@ -1411,22 +1582,20 @@ describe('IndexexchangeAdapter', function () { }); it('should not add fpd data to r object if it exceeds maximum request', function () { - config.setConfig({ - ortb2: { - site: { - keywords: 'power tools, drills', - search: 'drill', - }, - user: { - keywords: Array(1000).join('#'), - } + const ortb2 = { + site: { + keywords: 'power tools, drills', + search: 'drill', + }, + user: { + keywords: Array(1000).join('#'), } - }); + }; const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; - const request = spec.buildRequests([bid])[0]; + const request = spec.buildRequests([bid], {ortb2})[0]; const r = JSON.parse(request.data.r); expect(r.site.ref).to.exist; @@ -1496,6 +1665,70 @@ describe('IndexexchangeAdapter', function () { expect(gpid).to.equal(GPID); }); + it('should still build gpid in request if ortb2Imp.ext.gpid does not exist', function () { + const AD_UNIT_CODE = '/1111/home'; + const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); + sinon.stub(utils, 'getGptSlotInfoForAdUnitCode') + .returns({ gptSlot: AD_UNIT_CODE, divId: 'adunit-code-3-div-id' }); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam', + adslot: AD_UNIT_CODE + } + } + } + } + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const { ext: { gpid } } = JSON.parse(requests[0].data.r).imp[0]; + utils.getGptSlotInfoForAdUnitCode.restore(); + expect(gpid).to.equal(`${AD_UNIT_CODE}#adunit-code-3-div-id`); + }); + + it('should not build gpid if divid doesnt exist when ortb2Imp.ext.gpid does not exist', function () { + const AD_UNIT_CODE = '/1111/home'; + const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); + sinon.stub(utils, 'getGptSlotInfoForAdUnitCode') + .returns({ gptSlot: AD_UNIT_CODE }); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam', + adslot: AD_UNIT_CODE + } + } + } + } + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const imp = JSON.parse(requests[0].data.r).imp[0]; + utils.getGptSlotInfoForAdUnitCode.restore(); + expect(imp.ext.gpid).to.be.undefined; + expect(imp.ext.dfp_ad_unit_code).to.equal(AD_UNIT_CODE) + }); + + it('should not build gpid if dfp ad unit code / divid doesnt exist when ortb2Imp.ext.gpid does not exist', function () { + const AD_UNIT_CODE = '/1111/home'; + const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); + sinon.stub(utils, 'getGptSlotInfoForAdUnitCode') + .returns({}); + validBids[0].ortb2Imp = { + ext: { + data: { + adserver: { + name: 'gam', + } + } + } + } + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + + const imp = JSON.parse(requests[0].data.r).imp[0]; + utils.getGptSlotInfoForAdUnitCode.restore(); + expect(imp.ext).to.be.undefined; + }); + it('should send gpid in request if ortb2Imp.ext.gpid exists when no size present', function () { const validBids = utils.deepClone(DEFAULT_BANNER_VALID_BID_PARAM_NO_SIZE); validBids[0].ortb2Imp = { @@ -1564,7 +1797,7 @@ describe('IndexexchangeAdapter', function () { const payload = JSON.parse(query.r); expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); expect(payload.id).to.be.a('string'); - expect(payload.site.page).to.equal(DEFAULT_OPTION.refererInfo.referer); + expect(payload.site.page).to.equal(DEFAULT_OPTION.refererInfo.page); expect(payload.site.ref).to.equal(document.referrer); expect(payload.ext.source).to.equal('prebid'); expect(payload.source.ext.schain).to.deep.equal(SAMPLE_SCHAIN); @@ -1572,14 +1805,6 @@ describe('IndexexchangeAdapter', function () { expect(payload.imp).to.have.lengthOf(1); }); - it('payload should set site.page to pageUrl when it exists in config object', function () { - const url = 'https://example.com/index.html'; - config.setConfig({ pageUrl: url }); - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0].data; - const payload = JSON.parse(request.r); - expect(payload.site.page).to.contains(url); - }); - it('payload should have correct format and value for r.id when bidderRequestId is a number ', function () { const bidWithIntId = utils.deepClone(DEFAULT_BANNER_VALID_BID); bidWithIntId[0].bidderRequestId = 123456; @@ -1615,6 +1840,8 @@ describe('IndexexchangeAdapter', function () { expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); expect(impression.banner.format).to.be.length(2); expect(impression.banner.topframe).to.be.oneOf([0, 1]); + expect(impression.banner.pos).to.equal(0); + expect(impression.ext.tid).to.equal(DEFAULT_BANNER_VALID_BID[0].transactionId); impression.banner.format.map(({ w, h, ext }, index) => { const size = DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[index]; @@ -1648,7 +1875,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(ONE_VIDEO[0]); const expectedFloor = 3.25; bid.getFloor = () => ({ floor: expectedFloor, currency }); - const request = spec.buildRequests([bid])[0]; + const request = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.bidfloor).to.equal(expectedFloor); @@ -1660,7 +1887,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]) const expectedFloor = 3.25; bid.getFloor = () => ({ floor: expectedFloor, currency }); - const request = spec.buildRequests([bid])[0]; + const request = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.bidfloor).to.equal(expectedFloor); @@ -1672,7 +1899,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(ONE_BANNER[0]); bid.params.bidFloor = highFloor; bid.params.bidFloorCur = 'USD' - const request = spec.buildRequests([bid])[0]; + const request = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.bidfloor).to.equal(highFloor); @@ -1686,7 +1913,7 @@ describe('IndexexchangeAdapter', function () { bid.params.bidFloorCur = 'USD'; const expectedFloor = highFloor; bid.getFloor = () => ({ floor: expectedFloor, currency }); - const requestBidFloor = spec.buildRequests([bid])[0]; + const requestBidFloor = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(requestBidFloor.data.r).imp[0]; expect(impression.bidfloor).to.equal(highFloor); @@ -1698,7 +1925,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; bid.params.bidFloorCur = 'USD'; - const requestBidFloor = spec.buildRequests([bid])[0]; + const requestBidFloor = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(requestBidFloor.data.r).imp[0]; expect(impression.bidfloor).to.equal(bid.params.bidFloor); @@ -1715,7 +1942,7 @@ describe('IndexexchangeAdapter', function () { sinon.spy(bid, 'getFloor'); - const requestBidFloor = spec.buildRequests([bid])[0]; + const requestBidFloor = spec.buildRequests([bid], {})[0]; expect(bid.getFloor.getCall(0).args[0].mediaType).to.equal('banner'); expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); @@ -1749,7 +1976,7 @@ describe('IndexexchangeAdapter', function () { sinon.spy(bid, 'getFloor'); - const requestBidFloor = spec.buildRequests([bid])[0]; + const requestBidFloor = spec.buildRequests([bid], {})[0]; expect(bid.getFloor.getCall(0).args[0].mediaType).to.equal('banner'); expect(bid.getFloor.getCall(0).args[0].size[0]).to.equal(300); expect(bid.getFloor.getCall(0).args[0].size[1]).to.equal(250); @@ -1763,7 +1990,7 @@ describe('IndexexchangeAdapter', function () { it('impression should have sid if id is configured as number', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.id = 50; - const requestBidFloor = spec.buildRequests([bid])[0]; + const requestBidFloor = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(requestBidFloor.data.r).imp[0]; expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); @@ -1777,7 +2004,7 @@ describe('IndexexchangeAdapter', function () { it('impression should have sid if id is configured as string', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.id = 'abc'; - const requestBidFloor = spec.buildRequests([bid])[0]; + const requestBidFloor = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(requestBidFloor.data.r).imp[0]; expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); @@ -1807,7 +2034,7 @@ describe('IndexexchangeAdapter', function () { const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestWithFirstPartyData.data.r).site.page; - const expectedPageUrl = DEFAULT_OPTION.refererInfo.referer + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; + const expectedPageUrl = DEFAULT_OPTION.refererInfo.page + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; expect(pageUrl).to.equal(expectedPageUrl); }); @@ -1821,7 +2048,7 @@ describe('IndexexchangeAdapter', function () { const requestFirstPartyDataNumber = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestFirstPartyDataNumber.data.r).site.page; - expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); + expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.page); }); it('should not set first party or timeout if it is not present', function () { @@ -1832,7 +2059,7 @@ describe('IndexexchangeAdapter', function () { const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; - expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); + expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.page); expect(requestWithoutConfig.data.t).to.be.undefined; }); @@ -1840,7 +2067,7 @@ describe('IndexexchangeAdapter', function () { const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; - expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); + expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.page); expect(requestWithoutConfig.data.t).to.be.undefined; }); @@ -1850,7 +2077,7 @@ describe('IndexexchangeAdapter', function () { timeout: 500 } }); - const requestWithTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const requestWithTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {})[0]; expect(requestWithTimeout.data.t).to.equal(500); }); @@ -1861,14 +2088,14 @@ describe('IndexexchangeAdapter', function () { timeout: '500' } }); - const requestStringTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; + const requestStringTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {})[0]; expect(requestStringTimeout.data.t).to.be.undefined; }); }); describe('request should contain both banner and video requests', function () { - const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]]); + const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]], {}); it('should have banner request', () => { const bannerImpression = JSON.parse(request[0].data.r).imp[0]; @@ -1900,6 +2127,39 @@ describe('IndexexchangeAdapter', function () { }); }); + describe('request should contain both banner and native requests', function () { + const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_NATIVE_VALID_BID[0]]); + + it('should have banner request', () => { + const bannerImpression = JSON.parse(request[0].data.r).imp[0]; + + expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(1); + expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); + expect(bannerImpression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); + + expect(bannerImpression.banner.format).to.be.length(2); + expect(bannerImpression.banner.topframe).to.be.oneOf([0, 1]); + + bannerImpression.banner.format.map(({ w, h, ext }, index) => { + const size = DEFAULT_BANNER_VALID_BID[0].mediaTypes.banner.sizes[index]; + const sidValue = utils.parseGPTSingleSizeArray(size); + + expect(w).to.equal(size[0]); + expect(h).to.equal(size[1]); + expect(ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); + expect(ext.sid).to.equal(sidValue); + }); + }); + + it('should have native request', () => { + const nativeImpression = JSON.parse(request[1].data.r).imp[0]; + + expect(request[1].data.hasOwnProperty('v')).to.equal(false); + expect(nativeImpression.id).to.equal(DEFAULT_NATIVE_VALID_BID[0].bidId); + expect(nativeImpression.native).to.deep.equal(DEFAULT_NATIVE_IMP); + }); + }); + it('single request under 8k size limit for large ad unit', function () { const options = {}; const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); @@ -2065,15 +2325,7 @@ describe('IndexexchangeAdapter', function () { }); describe('detect missing sizes', function () { - beforeEach(function () { - config.setConfig({ - ix: { - detectMissingSizes: false - } - }); - }) - - it('request should not contain missing sizes if detectMissingSizes = false', function () { + it('request should always contain missing sizes', function () { const bid1 = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid1.mediaTypes.banner.sizes = LARGE_SET_OF_SIZES; @@ -2125,7 +2377,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; bid.mediaTypes.video.placement = 2; - const request = spec.buildRequests([bid])[0]; + const request = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); @@ -2135,7 +2387,7 @@ describe('IndexexchangeAdapter', function () { it('should set correct default placement, if context is instream', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'instream'; - const request = spec.buildRequests([bid])[0]; + const request = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); @@ -2145,7 +2397,7 @@ describe('IndexexchangeAdapter', function () { it('should set correct default placement, if context is outstream', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; - const request = spec.buildRequests([bid])[0]; + const request = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); @@ -2155,7 +2407,7 @@ describe('IndexexchangeAdapter', function () { it('should handle unexpected context', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'not-valid'; - const request = spec.buildRequests([bid])[0]; + const request = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.video.placement).to.be.undefined; }); @@ -2165,7 +2417,7 @@ describe('IndexexchangeAdapter', function () { bid.mediaTypes.video.context = 'outstream'; bid.mediaTypes.video.protocols = [1]; bid.mediaTypes.video.mimes = ['video/override']; - const request = spec.buildRequests([bid])[0]; + const request = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.video.protocols[0]).to.equal(2); @@ -2176,7 +2428,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); bid.mediaTypes.video.context = 'outstream'; bid.mediaTypes.video.random = true; - const request = spec.buildRequests([bid])[0]; + const request = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.video.random).to.not.exist; @@ -2190,11 +2442,13 @@ describe('IndexexchangeAdapter', function () { bid.mediaTypes.video.protocols = [6]; bid.mediaTypes.video.mimes = ['video/mp4']; bid.mediaTypes.video.api = 2; - const request = spec.buildRequests([bid])[0]; + bid.mediaTypes.video.pos = 0; + const request = spec.buildRequests([bid], {})[0]; const impression = JSON.parse(request.data.r).imp[0]; expect(impression.video.protocols[0]).to.equal(6); expect(impression.video.api).to.equal(2); + expect(impression.video.pos).to.equal(0); expect(impression.video.mimes[0]).to.equal('video/mp4'); }); @@ -2212,7 +2466,7 @@ describe('IndexexchangeAdapter', function () { }); it('should build video request when if video obj is not provided at params level', () => { - const request = spec.buildRequests([DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0]]); + const request = spec.buildRequests([DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0]], {}); const videoImpression = JSON.parse(request[0].data.r).imp[0]; expect(JSON.parse(request[0].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); @@ -2220,11 +2474,199 @@ describe('IndexexchangeAdapter', function () { expect(videoImpression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0].mediaTypes.video.playerSize[0][0]); expect(videoImpression.video.h).to.equal(DEFAULT_VIDEO_VALID_BID_NO_VIDEO_PARAMS[0].mediaTypes.video.playerSize[0][1]); }); + + it('should set different placement for floating ad units', () => { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'outstream'; + bid.params.video.playerConfig = { + floatOnScroll: true + }; + + const request = spec.buildRequests([bid]); + const videoImpression = JSON.parse(request[0].data.r).imp[0]; + + expect(videoImpression.video.placement).to.eq(5); + }) + }); + + describe('buildRequestNative', function () { + it('should build request with expected params', function() { + const request = spec.buildRequests(DEFAULT_NATIVE_VALID_BID, DEFAULT_OPTION); + const query = request[0].data; + + expect(query.hasOwnProperty('v')).to.equal(false); + expect(query.s).to.equal(DEFAULT_NATIVE_VALID_BID[0].params.siteId); + expect(query.r).to.exist; + expect(query.ac).to.equal('j'); + expect(query.sd).to.equal(1); + expect(JSON.parse(query.r).at).to.equal(1); + }); + + it('should send gpid in request if ortb2Imp.ext.gpid exists', function () { + const GPID = '/19968336/some-adunit-path'; + const bids = utils.deepClone(DEFAULT_NATIVE_VALID_BID); + bids[0].ortb2Imp = { + ext: { + gpid: GPID + } + }; + const requests = spec.buildRequests(bids, DEFAULT_OPTION); + const { ext: { gpid } } = JSON.parse(requests[0].data.r).imp[0]; + expect(gpid).to.equal(GPID); + }); + + it('should build request with required asset properties with default values', function() { + const request = spec.buildRequests(DEFAULT_NATIVE_VALID_BID, DEFAULT_OPTION); + const nativeImpression = JSON.parse(request[0].data.r).imp[0]; + + expect(request[0].data.hasOwnProperty('v')).to.equal(false); + expect(nativeImpression.id).to.equal(DEFAULT_NATIVE_VALID_BID[0].bidId); + expect(nativeImpression.native).to.deep.equal(DEFAULT_NATIVE_IMP); + }); + + it('should build request with given asset properties', function() { + let bid = utils.deepClone(DEFAULT_NATIVE_VALID_BID) + bid[0].nativeOrtbRequest = { + assets: [{id: 0, required: 0, title: {len: 140}}, {id: 1, required: 0, video: {mimes: ['javascript'], minduration: 10, maxduration: 60, protocols: [1]}}] + } + const request = spec.buildRequests(bid, DEFAULT_OPTION); + const nativeImpression = JSON.parse(request[0].data.r).imp[0]; + expect(nativeImpression.native).to.deep.equal({request: '{"assets":[{"id":0,"required":0,"title":{"len":140}},{"id":1,"required":0,"video":{"mimes":["javascript"],"minduration":10,"maxduration":60,"protocols":[1]}}],"eventtrackers":[{"event":1,"methods":[1,2]}],"privacy":1}', ver: '1.2'}); + }); + + it('should build request with all possible Prebid asset properties', function() { + let bid = utils.deepClone(DEFAULT_NATIVE_VALID_BID) + bid[0].nativeOrtbRequest = { + 'ver': '1.2', + 'assets': [ + { + 'id': 0, + 'required': 0, + 'title': { + 'len': 140 + } + }, + { + 'id': 1, + 'required': 0, + 'data': { + 'type': 2 + } + }, + { + 'id': 2, + 'required': 0, + 'data': { + 'type': 10 + } + }, + { + 'id': 3, + 'required': 0, + 'data': { + 'type': 1 + } + }, + { + 'id': 4, + 'required': 0, + 'img': { + 'type': 1 + } + }, + { + 'id': 5, + 'required': 0, + 'img': { + 'type': 3 + } + }, + { + 'id': 6, + 'required': 0 + }, + { + 'id': 7, + 'required': 0, + 'data': { + 'type': 11 + } + }, + { + 'id': 8, + 'required': 0 + }, + { + 'id': 9, + 'required': 0 + }, + { + 'id': 10, + 'required': 0, + 'data': { + 'type': 12 + } + }, + { + 'id': 11, + 'required': 0, + 'data': { + 'type': 3 + } + }, + { + 'id': 12, + 'required': 0, + 'data': { + 'type': 5 + } + }, + { + 'id': 13, + 'required': 0, + 'data': { + 'type': 4 + } + }, + { + 'id': 14, + 'required': 0, + 'data': { + 'type': 6 + } + }, + { + 'id': 15, + 'required': 0, + 'data': { + 'type': 7 + } + }, + { + 'id': 16, + 'required': 0, + 'data': { + 'type': 9 + } + }, + { + 'id': 17, + 'required': 0, + 'data': { + 'type': 8 + } + } + ] + } + const request = spec.buildRequests(bid, DEFAULT_OPTION); + const nativeImpression = JSON.parse(request[0].data.r).imp[0]; + expect(nativeImpression.native).to.deep.equal({request: '{"ver":"1.2","assets":[{"id":0,"required":0,"title":{"len":140}},{"id":1,"required":0,"data":{"type":2}},{"id":2,"required":0,"data":{"type":10}},{"id":3,"required":0,"data":{"type":1}},{"id":4,"required":0,"img":{"type":1}},{"id":5,"required":0,"img":{"type":3}},{"id":6,"required":0},{"id":7,"required":0,"data":{"type":11}},{"id":8,"required":0},{"id":9,"required":0},{"id":10,"required":0,"data":{"type":12}},{"id":11,"required":0,"data":{"type":3}},{"id":12,"required":0,"data":{"type":5}},{"id":13,"required":0,"data":{"type":4}},{"id":14,"required":0,"data":{"type":6}},{"id":15,"required":0,"data":{"type":7}},{"id":16,"required":0,"data":{"type":9}},{"id":17,"required":0,"data":{"type":8}}],"eventtrackers":[{"event":1,"methods":[1,2]}],"privacy":1}', ver: '1.2'}); + }) }); describe('buildRequestMultiFormat', function () { it('only banner bidder params set', function () { - const request = spec.buildRequests(DEFAULT_MULTIFORMAT_BANNER_VALID_BID) + const request = spec.buildRequests(DEFAULT_MULTIFORMAT_BANNER_VALID_BID, {}) const bannerImpression = JSON.parse(request[0].data.r).imp[0]; expect(JSON.parse(request[0].data.r).imp).to.have.lengthOf(1); expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); @@ -2235,7 +2677,7 @@ describe('IndexexchangeAdapter', function () { describe('only video bidder params set', function () { it('should generate video impression', function () { - const request = spec.buildRequests(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID); + const request = spec.buildRequests(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID, {}); const videoImp = JSON.parse(request[1].data.r).imp[0]; expect(JSON.parse(request[1].data.r).imp).to.have.lengthOf(1); expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); @@ -2243,20 +2685,11 @@ describe('IndexexchangeAdapter', function () { expect(videoImp.video.w).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[0]); expect(videoImp.video.h).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.size[1]); }); - - it('should get missing sizes count 0 when params.size not used', function () { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - delete bid.params.size; - const request = spec.buildRequests([bid]); - const diagObj = JSON.parse(request[0].data.r).ext.ixdiag; - expect(diagObj.msd).to.equal(0); - expect(diagObj.msi).to.equal(0); - }); }); describe('both banner and video bidder params set', function () { const bids = [DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]]; - const request = spec.buildRequests(bids); + const request = spec.buildRequests(bids, {}); it('should return valid banner requests', function () { const impressions = JSON.parse(request[0].data.r).imp; @@ -2303,6 +2736,9 @@ describe('IndexexchangeAdapter', function () { expect(diagObj.allu).to.equal(2); expect(diagObj.version).to.equal('$prebid.version$'); expect(diagObj.url).to.equal('http://localhost:9876/context.html') + expect(diagObj.pbadslot).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].ortb2Imp.ext.data.pbadslot) + expect(diagObj.tagid).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].params.tagId) + expect(diagObj.adunitcode).to.equal(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0].adUnitCode) }); }); }); @@ -2663,7 +3099,7 @@ describe('IndexexchangeAdapter', function () { const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); - expect(requestWithoutreferInfo.site.page).to.equal(options.refererInfo.referer); + expect(requestWithoutreferInfo.site.page).to.equal(options.refererInfo.page); expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); }); @@ -2686,6 +3122,96 @@ describe('IndexexchangeAdapter', function () { expect(bannerResult[0].ttl).to.equal(300); expect(videoResult[0].ttl).to.equal(3600); }); + + it('should get correct bid response for native ad', function () { + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + mediaType: 'native', + width: 1, + height: 1, + currency: 'USD', + netRevenue: true, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'] + }, + native: { + ortb: { + assets: [ + { + 'id': 0, + 'img': { + 'h': 250, + 'url': 'https://cdn.liftoff.io/customers/1209/creatives/2501-icon-250x250.png', + 'w': 250 + } + }, + { + 'id': 1, + 'img': { + 'h': 627, + 'url': 'https://cdn.liftoff.io/customers/5a9cab9cc6/image/lambda_png/a0355879b06c09b09232.png', + 'w': 1200 + } + }, + { + 'data': { + 'value': 'autodoc.co.uk' + }, + 'id': 2 + }, + { + 'data': { + 'value': 'Les pièces automobiles dont vous avez besoin, toujours sous la main.' + }, + 'id': 3 + }, + { + 'id': 4, + 'title': { + 'text': 'Autodoc' + } + }, + { + 'id': 5, + 'video': { + 'vasttag': 'blah' + } + } + ], + 'eventtrackers': [ + { + 'event': 1, + 'method': 1, + 'url': 'https://impression-europe.liftoff.io/index/impression' + }, + { + 'event': 1, + 'method': 1, + 'url': 'https://a701.casalemedia.com/impression/v1' + } + ], + 'link': { + 'clicktrackers': [ + 'https://click.liftoff.io/v1/campaign_click/blah' + ], + 'url': 'https://play.google.com/store/apps/details?id=de.autodoc.gmbh' + }, + 'privacy': 'https://privacy.link.com', + 'ver': '1.2' + } + }, + ttl: 3600 + } + ]; + const result = spec.interpretResponse({ body: DEFAULT_NATIVE_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA, validBidRequests: [] }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); }); describe('bidrequest consent', function () { @@ -2842,7 +3368,7 @@ describe('IndexexchangeAdapter', function () { sandbox.restore(); config.setConfig({ - fpd: {}, + ortb2: {}, ix: {}, }) }); @@ -2905,7 +3431,7 @@ describe('IndexexchangeAdapter', function () { }); expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid]); + spec.buildRequests([bid], {}); expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE]: 2 } }); }); @@ -2914,25 +3440,24 @@ describe('IndexexchangeAdapter', function () { bid.bidderRequestId = Array(8000).join('#'); expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid]); + spec.buildRequests([bid], {}); expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.EXCEEDS_MAX_SIZE]: 2 } }); }); it('should log ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - - config.setConfig({ - fpd: { - site: { + const ortb2 = { + site: { + ext: { data: { pageType: Array(5700).join('#') } } } - }); + }; expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid]); + spec.buildRequests([bid], {ortb2}); expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE]: 2 } }); }); @@ -2943,7 +3468,7 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.be.true; spec.buildRequests([bid]); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.VIDEO_DURATION_INVALID]: 3 } }); + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.VIDEO_DURATION_INVALID]: 2 } }); }); it('should increment errors for errorCode', () => { @@ -3030,6 +3555,7 @@ describe('IndexexchangeAdapter', function () { it('should not save error data into localstorage if consent is not given', () => { config.setConfig({ deviceAccess: false }); + storage.localStorageIsEnabled.restore(); // let core manage device access const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); bid.params.size = ['400', 100]; expect(spec.isBidRequestValid(bid)).to.be.false; diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index bc44e594864..0c999f1cd51 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -199,16 +199,16 @@ describe('jixie Adapter', function () { let getCookieStub = sinon.stub(storage, 'getCookie'); let getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); getCookieStub - .withArgs('_jx') + .withArgs('_jxx') .returns(clientIdTest1_); getCookieStub - .withArgs('_jxs') + .withArgs('_jxxs') .returns(sessionIdTest1_); getLocalStorageStub - .withArgs('_jx') + .withArgs('_jxx') .returns(clientIdTest1_); getLocalStorageStub - .withArgs('_jxs') + .withArgs('_jxxs') .returns(sessionIdTest1_ ); let miscDimsStub = sinon.stub(jixieaux, 'getMiscDims'); @@ -528,10 +528,10 @@ describe('jixie Adapter', function () { let setCookieSpy = sinon.spy(storage, 'setCookie'); let setLocalStorageSpy = sinon.spy(storage, 'setDataInLocalStorage'); const result = spec.interpretResponse({body: responseBody_}, requestObj_) - expect(setLocalStorageSpy.calledWith('_jx', '43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); - expect(setLocalStorageSpy.calledWith('_jxs', '1600057934-43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); - expect(setCookieSpy.calledWith('_jxs', '1600057934-43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); - expect(setCookieSpy.calledWith('_jx', '43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); + expect(setLocalStorageSpy.calledWith('_jxx', '43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); + expect(setLocalStorageSpy.calledWith('_jxxs', '1600057934-43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); + expect(setCookieSpy.calledWith('_jxxs', '1600057934-43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); + expect(setCookieSpy.calledWith('_jxx', '43aacc10-f643-11ea-8a10-c5fe2d394e7e')).to.equal(true); // video ad with vastUrl returned by adserver expect(result[0].requestId).to.equal('62847e4c696edcb') diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 43103d953d6..77c8ce58442 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -1,8 +1,9 @@ import { fetchTargetingForMediaId, getVatFromCache, extractPublisherParams, formatTargetingResponse, getVatFromPlayer, enrichAdUnits, addTargetingToBid, - fetchTargetingInformation, jwplayerSubmodule, getContentId, getContentSegments, getContentData, getOrtbSiteContent } from 'modules/jwplayerRtdProvider.js'; + fetchTargetingInformation, jwplayerSubmodule, getContentId, getContentSegments, getContentData, addOrtbSiteContent } from 'modules/jwplayerRtdProvider.js'; import { server } from 'test/mocks/xhr.js'; import { config as prebidConfig } from 'src/config.js'; +import {deepClone} from '../../../src/utils.js'; describe('jwplayerRtdProvider', function() { const testIdForSuccess = 'test_id_for_success'; @@ -435,17 +436,13 @@ describe('jwplayerRtdProvider', function() { bids }; - enrichAdUnits([adUnit]); + const ortb2Fragments = {global: {}}; + enrichAdUnits([adUnit], ortb2Fragments); const bid1 = bids[0]; const bid2 = bids[1]; expect(bid1).to.not.have.property('rtd'); expect(bid2).to.not.have.property('rtd'); - let updatedConfig; - const setConfigSub = sinon.stub(prebidConfig, 'setConfig').callsFake(function (config) { - updatedConfig = config; - }); - const request = fakeServer.requests[0]; request.respond( 200, @@ -460,12 +457,11 @@ describe('jwplayerRtdProvider', function() { }) ); - expect(updatedConfig).to.have.property('ortb2'); - expect(updatedConfig.ortb2).to.have.property('site'); - expect(updatedConfig.ortb2.site).to.have.property('content'); - expect(updatedConfig.ortb2.site.content).to.have.property('id', 'jw_' + testIdForSuccess); - expect(updatedConfig.ortb2.site.content).to.have.property('data'); - const data = updatedConfig.ortb2.site.content.data; + expect(ortb2Fragments.global).to.have.property('site'); + expect(ortb2Fragments.global.site).to.have.property('content'); + expect(ortb2Fragments.global.site.content).to.have.property('id', 'jw_' + testIdForSuccess); + expect(ortb2Fragments.global.site.content).to.have.property('data'); + const data = ortb2Fragments.global.site.content.data; expect(data).to.have.length(1); const datum = data[0]; expect(datum).to.have.property('name', 'jwplayer.com'); @@ -476,8 +472,6 @@ describe('jwplayerRtdProvider', function() { const segment2 = datum.segment[1]; expect(segment1).to.have.property('id', 'test_seg_1'); expect(segment2).to.have.property('id', 'test_seg_2'); - - setConfigSub.restore(); }); it('should remove obsolete jwplayer data', function () { @@ -500,16 +494,8 @@ describe('jwplayerRtdProvider', function() { bids }; - enrichAdUnits([adUnit]); - const bid1 = bids[0]; - expect(bid1).to.not.have.property('rtd'); - - let updatedConfig; - const setConfigSub = sinon.stub(prebidConfig, 'setConfig').callsFake(function (config) { - updatedConfig = config; - }); - const getConfigSub = sinon.stub(prebidConfig, 'getConfig').callsFake(function () { - return { + const ortb2Fragments = { + global: { site: { content: { id: 'randomContentId', @@ -526,7 +512,11 @@ describe('jwplayerRtdProvider', function() { } } } - }); + }; + + enrichAdUnits([adUnit], ortb2Fragments); + const bid1 = bids[0]; + expect(bid1).to.not.have.property('rtd'); const request = fakeServer.requests[0]; request.respond( @@ -542,12 +532,11 @@ describe('jwplayerRtdProvider', function() { }) ); - expect(updatedConfig).to.have.property('ortb2'); - expect(updatedConfig.ortb2).to.have.property('site'); - expect(updatedConfig.ortb2.site).to.have.property('content'); - expect(updatedConfig.ortb2.site.content).to.have.property('id', 'jw_' + testIdForSuccess); - expect(updatedConfig.ortb2.site.content).to.have.property('data'); - const data = updatedConfig.ortb2.site.content.data; + expect(ortb2Fragments.global).to.have.property('site'); + expect(ortb2Fragments.global.site).to.have.property('content'); + expect(ortb2Fragments.global.site.content).to.have.property('id', 'jw_' + testIdForSuccess); + expect(ortb2Fragments.global.site.content).to.have.property('data'); + const data = ortb2Fragments.global.site.content.data; expect(data).to.have.length(3); const randomDatum = data[0]; @@ -567,9 +556,6 @@ describe('jwplayerRtdProvider', function() { const segment2 = jwplayerDatum.segment[1]; expect(segment1).to.have.property('id', 'test_seg_1'); expect(segment2).to.have.property('id', 'test_seg_2'); - - setConfigSub.restore(); - getConfigSub.restore(); }); }); @@ -718,152 +704,141 @@ describe('jwplayerRtdProvider', function() { }); describe(' Add Ortb Site Content', function () { - it('should return undefined when id and data params are empty', function () { - const bid = { - ortb2: { - site: { - content: { - id: 'randomId' - }, - random: { - random_sub: 'randomSub' - } + it('should maintain object structure when id and data params are empty', function () { + const ortb2 = { + site: { + content: { + id: 'randomId' }, - app: { - content: { - id: 'appId' - } + random: { + random_sub: 'randomSub' + } + }, + app: { + content: { + id: 'appId' } } - }; - const ortb = getOrtbSiteContent(bid.ortb2); - expect(ortb).to.be.undefined; + } + const copy = deepClone(ortb2); + addOrtbSiteContent(copy); + expect(copy).to.eql(ortb2); }); it('should create a structure compliant with the oRTB 2 spec', function() { - const bid = {}; + const ortb2 = {} const expectedId = 'expectedId'; const expectedData = { datum: 'datum' }; - const ortb = getOrtbSiteContent(bid.ortb2, expectedId, expectedData); - expect(ortb).to.have.nested.property('site.content.id', expectedId); - expect(ortb).to.have.nested.property('site.content.data'); - expect(ortb.site.content.data[0]).to.be.deep.equal(expectedData); + addOrtbSiteContent(ortb2, expectedId, expectedData); + expect(ortb2).to.have.nested.property('site.content.id', expectedId); + expect(ortb2).to.have.nested.property('site.content.data'); + expect(ortb2.site.content.data[0]).to.be.deep.equal(expectedData); }); it('should respect existing structure when adding adding fields', function () { - const bid = { - ortb2: { - site: { - content: { - id: 'oldId' - }, - random: { - random_sub: 'randomSub' - } + const ortb2 = { + site: { + content: { + id: 'oldId' }, - app: { - content: { - id: 'appId' - } + random: { + random_sub: 'randomSub' + } + }, + app: { + content: { + id: 'appId' } } }; const expectedId = 'expectedId'; const expectedData = { datum: 'datum' }; - const ortb = getOrtbSiteContent(bid.ortb2, expectedId, expectedData); - expect(ortb).to.have.nested.property('site.random.random_sub', 'randomSub'); - expect(ortb).to.have.nested.property('app.content.id', 'appId'); - expect(ortb).to.have.nested.property('site.content.id', expectedId); - expect(ortb).to.have.nested.property('site.content.data'); - expect(ortb.site.content.data[0]).to.be.deep.equal(expectedData); + addOrtbSiteContent(ortb2, expectedId, expectedData); + expect(ortb2).to.have.nested.property('site.random.random_sub', 'randomSub'); + expect(ortb2).to.have.nested.property('app.content.id', 'appId'); + expect(ortb2).to.have.nested.property('site.content.id', expectedId); + expect(ortb2).to.have.nested.property('site.content.data'); + expect(ortb2.site.content.data[0]).to.be.deep.equal(expectedData); }); it('should set content id', function () { - const bid = {}; + const ortb2 = {}; const expectedId = 'expectedId'; - const ortb = getOrtbSiteContent(bid.ortb2, expectedId); - expect(ortb).to.have.nested.property('site.content.id', expectedId); + addOrtbSiteContent(ortb2, expectedId); + expect(ortb2).to.have.nested.property('site.content.id', expectedId); }); it('should override content id', function () { - const bid = { - ortb2: { - site: { - content: { - id: 'oldId' - } + const ortb2 = { + site: { + content: { + id: 'oldId' } } }; const expectedId = 'expectedId'; - const ortb = getOrtbSiteContent(bid.ortb2, expectedId); - expect(ortb).to.have.nested.property('site.content.id', expectedId); + addOrtbSiteContent(ortb2, expectedId); + expect(ortb2).to.have.nested.property('site.content.id', expectedId); }); it('should keep previous content id when not set', function () { const previousId = 'oldId'; - const bid = { - ortb2: { - site: { - content: { - id: previousId, - data: [{ datum: 'first_datum' }] - } + const ortb2 = { + site: { + content: { + id: previousId, + data: [{ datum: 'first_datum' }] } } }; - const ortb = getOrtbSiteContent(bid.ortb2, null, { datum: 'new_datum' }); - expect(ortb).to.have.nested.property('site.content.id', previousId); + addOrtbSiteContent(ortb2, null, { datum: 'new_datum' }); + expect(ortb2).to.have.nested.property('site.content.id', previousId); }); it('should set content data', function () { - const bid = {}; + const ortb2 = {}; const expectedData = { datum: 'datum' }; - const ortb = getOrtbSiteContent(bid.ortb2, null, expectedData); - expect(ortb).to.have.nested.property('site.content.data'); - expect(ortb.site.content.data).to.have.length(1); - expect(ortb.site.content.data[0]).to.be.deep.equal(expectedData); + addOrtbSiteContent(ortb2, null, expectedData); + expect(ortb2).to.have.nested.property('site.content.data'); + expect(ortb2.site.content.data).to.have.length(1); + expect(ortb2.site.content.data[0]).to.be.deep.equal(expectedData); }); it('should append content data', function () { - const bid = { - ortb2: { - site: { - content: { - data: [{ datum: 'first_datum' }] - } + const ortb2 = { + site: { + content: { + data: [{ datum: 'first_datum' }] } } }; const expectedData = { datum: 'datum' }; - const ortb = getOrtbSiteContent(bid.ortb2, null, expectedData); - expect(ortb).to.have.nested.property('site.content.data'); - expect(ortb.site.content.data).to.have.length(2); - expect(ortb.site.content.data.pop()).to.be.deep.equal(expectedData); + addOrtbSiteContent(ortb2, null, expectedData); + expect(ortb2).to.have.nested.property('site.content.data'); + expect(ortb2.site.content.data).to.have.length(2); + expect(ortb2.site.content.data.pop()).to.be.deep.equal(expectedData); }); it('should keep previous data when not set', function () { const expectedId = 'expectedId'; const expectedData = { datum: 'first_datum' }; - const bid = { - ortb2: { - site: { - content: { - data: [expectedData] - } + const ortb2 = { + site: { + content: { + data: [expectedData] } } }; - const ortb = getOrtbSiteContent(bid.ortb2, expectedId); - expect(ortb).to.have.nested.property('site.content.data'); - expect(ortb.site.content.data).to.have.length(1); - expect(ortb.site.content.data[0]).to.be.deep.equal(expectedData); - expect(ortb).to.have.nested.property('site.content.id', expectedId); + addOrtbSiteContent(ortb2, expectedId); + expect(ortb2).to.have.nested.property('site.content.data'); + expect(ortb2.site.content.data).to.have.length(1); + expect(ortb2.site.content.data[0]).to.be.deep.equal(expectedData); + expect(ortb2).to.have.nested.property('site.content.id', expectedId); }); }); diff --git a/test/spec/modules/kargoAnalyticsAdapter_spec.js b/test/spec/modules/kargoAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..c27c8499aa1 --- /dev/null +++ b/test/spec/modules/kargoAnalyticsAdapter_spec.js @@ -0,0 +1,42 @@ +import kargoAnalyticsAdapter from 'modules/kargoAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('Kargo Analytics Adapter', function () { + const adapterConfig = { + provider: 'kargoAnalytics', + }; + + after(function () { + kargoAnalyticsAdapter.disableAnalytics(); + }); + + describe('main test flow', function () { + beforeEach(function () { + kargoAnalyticsAdapter.enableAnalytics(adapterConfig); + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + events.getEvents.restore(); + }); + + it('bid response data should send one request with auction ID, auction timeout, and response time', function() { + const bidResponse = { + bidder: 'kargo', + auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f', + timeToRespond: 192, + }; + + events.emit(constants.EVENTS.AUCTION_INIT, { + timeout: 1000 + }); + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://krk.kargo.com/api/v1/event/auction-data?aid=66529d4c-8998-47c2-ab3e-5b953490b98f&ato=1000&rt=192&it=0'); + }); + }); +}); diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 4733c2dcdb3..169ee520f33 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -38,6 +38,11 @@ describe('kargo adapter tests', function () { var bids, undefinedCurrency, noAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + kargo: { + storageAllowed: true + } + }; undefinedCurrency = false; noAdServerCurrency = false; sandbox.stub(config, 'getConfig').callsFake(function(key) { @@ -96,6 +101,7 @@ describe('kargo adapter tests', function () { cookies.length = 0; localStorageItems.length = 0; + $$PREBID_GLOBAL$$.bidderSettings = {}; }); function setCookie(cname, cvalue, exdays = 1) { @@ -155,7 +161,7 @@ describe('kargo adapter tests', function () { } function getKrgCrbOldStyle() { - return '%7B%22v%22%3A%22eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0=%22%7D'; + return '{"v":"eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0="}'; } function initializeKrgCrb(cookieOnly) { @@ -182,7 +188,7 @@ describe('kargo adapter tests', function () { } function getInvalidKrgCrbType2OldStyle() { - return '%7B%22v%22%3A%22%26%26%26%26%26%26%22%7D'; + return '{"v":"&&&&&&"}'; } function initializeInvalidKrgCrbType2() { @@ -194,7 +200,7 @@ describe('kargo adapter tests', function () { } function getInvalidKrgCrbType3OldStyle() { - return '%7B%22v%22%3A%22Ly8v%22%7D'; + return '{"v":"Ly8v"}'; } function initializeInvalidKrgCrbType3Cookie() { @@ -202,7 +208,7 @@ describe('kargo adapter tests', function () { } function getInvalidKrgCrbType4OldStyle() { - return '%7B%22v%22%3A%22bnVsbA%3D%3D%22%7D'; + return '{"v":"bnVsbA=="}'; } function initializeInvalidKrgCrbType4Cookie() { @@ -214,7 +220,7 @@ describe('kargo adapter tests', function () { } function getEmptyKrgCrbOldStyle() { - return '%7B%22v%22%3A%22eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9%22%7D'; + return '{"v":"eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9"}'; } function initializeEmptyKrgCrb() { @@ -270,7 +276,7 @@ describe('kargo adapter tests', function () { optOut: false, usp: '1---' }, - pageURL: window.location.href, + pageURL: 'https://www.prebid.org', prebidRawBidRequests: [ { bidId: 1, @@ -321,10 +327,19 @@ describe('kargo adapter tests', function () { if (excludeTdid) { delete clonedBids[0].userId.tdid; } - var payload = { timeout: 200, uspConsent: '1---', foo: 'bar' }; + var payload = { + timeout: 200, + uspConsent: '1---', + foo: 'bar', + refererInfo: { + page: 'https://www.prebid.org', + }, + }; + if (gdpr) { payload['gdprConsent'] = gdpr } + var request = spec.buildRequests(clonedBids, payload); expected.sessionId = getSessionId(); sessionIds.push(expected.sessionId); @@ -566,6 +581,7 @@ describe('kargo adapter tests', function () { cpm: 2.5, width: 300, height: 250, + ad: '', vastXml: '', ttl: 300, creativeId: 'bar', @@ -586,6 +602,11 @@ describe('kargo adapter tests', function () { var shouldSimulateOutdatedBrowser, crb, isActuallyOutdatedBrowser; beforeEach(() => { + $$PREBID_GLOBAL$$.bidderSettings = { + kargo: { + storageAllowed: true + } + }; crb = {}; shouldSimulateOutdatedBrowser = false; isActuallyOutdatedBrowser = false; diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 8b45fead744..5bcf62ff95d 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -10,7 +10,7 @@ function createBidderRequest(auctionId, timeout, pageUrl) { auctionId: auctionId || 'c1243d83-0bed-4fdb-8c76-42b456be17d0', timeout: timeout || 2000, refererInfo: { - referer: pageUrl || 'example.com' + page: pageUrl || 'example.com' } }; } @@ -373,7 +373,7 @@ describe('KoblerAdapter', function () { const bidderRequest = { auctionId: 'c1243d83-0bed-4fdb-8c76-42b456be17d0', refererInfo: { - referer: 'example.com' + page: 'example.com' } }; config.setConfig({ @@ -729,12 +729,12 @@ describe('KoblerAdapter', function () { expect(utils.triggerPixel.getCall(0).args[0]).to.be.equal( 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code&' + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ef236c6c-e934-406b-a877-d7be8e8a839a&timeout=100&' + - 'page_url=' + encodeURIComponent(getRefererInfo().referer) + 'page_url=' + encodeURIComponent(getRefererInfo().page) ); expect(utils.triggerPixel.getCall(1).args[0]).to.be.equal( 'https://bid.essrtb.com/notify/prebid_timeout?ad_unit_code=adunit-code-2&' + 'auction_id=a1fba829-dd41-409f-acfb-b7b0ac5f30c6&bid_id=ca4121c8-9a4a-46ba-a624-e9b64af206f2&timeout=100&' + - 'page_url=' + encodeURIComponent(getRefererInfo().referer) + 'page_url=' + encodeURIComponent(getRefererInfo().page) ); }); }); diff --git a/test/spec/modules/konduitAnalyticsAdapter_spec.js b/test/spec/modules/konduitAnalyticsAdapter_spec.js index ac557d27f90..1612138cde4 100644 --- a/test/spec/modules/konduitAnalyticsAdapter_spec.js +++ b/test/spec/modules/konduitAnalyticsAdapter_spec.js @@ -121,6 +121,7 @@ describe(`Konduit Analytics Adapter`, () => { expect(requestBody.konduitId).to.be.equal(konduitId); expect(requestBody.prebidVersion).to.be.equal('$prebid.version$'); expect(requestBody.environment).to.be.an('object'); - sinon.assert.callCount(konduitAnalyticsAdapter.track, 6); + // 6 Konduit events + 1 Clean.io event + sinon.assert.callCount(konduitAnalyticsAdapter.track, 7); }); }); diff --git a/test/spec/modules/kubientBidAdapter_spec.js b/test/spec/modules/kubientBidAdapter_spec.js index f7afc709564..a6241aa8d41 100644 --- a/test/spec/modules/kubientBidAdapter_spec.js +++ b/test/spec/modules/kubientBidAdapter_spec.js @@ -91,7 +91,7 @@ describe('KubientAdapter', function () { auctionStart: 1472239426000, timeout: 5000, refererInfo: { - referer: 'http://www.example.com', + page: 'http://www.example.com', reachedTop: true, }, gdprConsent: { diff --git a/test/spec/modules/kueezBidAdapter_spec.js b/test/spec/modules/kueezBidAdapter_spec.js new file mode 100644 index 00000000000..cd95a9ebdc6 --- /dev/null +++ b/test/spec/modules/kueezBidAdapter_spec.js @@ -0,0 +1,482 @@ +import { expect } from 'chai'; +import { spec } from 'modules/kueezBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import * as utils from 'src/utils.js'; + +const ENDPOINT = 'https://hb.kueezssp.com/hb-kz-multi'; +const TEST_ENDPOINT = 'https://hb.kueezssp.com/hb-multi-kz-test'; +const TTL = 360; +/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ + +describe('kueezBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'params': { + 'org': 'test-publisher-id' + } + }; + + it('should return true when required params are passed', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'org': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'test-publisher-id' + }, + 'bidId': '5wfg9887sd5478', + 'loop': 1, + 'bidderRequestId': 'op87952ewq8567', + 'auctionId': '87se98rt-5789-8735-2546-t98yh5678231', + 'mediaTypes': { + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream' + } + }, + 'vastXml': '"..."' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'test-publisher-id' + }, + 'bidId': '5wfg9887sd5478', + 'loop': 1, + 'bidderRequestId': 'op87952ewq8567', + 'auctionId': '87se98rt-5789-8735-2546-t98yh5678231', + 'mediaTypes': { + 'banner': { + } + }, + 'ad': '""' + } + ]; + + const testModeBidRequests = [ + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[640, 480]], + 'params': { + 'org': 'test-publisher-id', + 'testMode': true + }, + 'bidId': '5wfg9887sd5478', + 'loop': 2, + 'bidderRequestId': 'op87952ewq8567', + 'auctionId': '87se98rt-5789-8735-2546-t98yh5678231', + } + ]; + + const bidderRequest = { + bidderCode: 'kueez', + } + const placementId = '12345678'; + + it('sends the placementId to ENDPOINT via POST', function () { + bidRequests[0].params.placementId = placementId; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].placementId).to.equal(placementId); + }); + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('sends bid request to TEST ENDPOINT via POST', function () { + const request = spec.buildRequests(testModeBidRequests, bidderRequest); + expect(request.url).to.equal(TEST_ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should send the correct bid Id', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].bidId).to.equal('5wfg9887sd5478'); + }); + + it('should send the correct sizes array', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].sizes).to.be.an('array'); + expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) + expect(request.data.bids[1].sizes).to.be.an('array'); + expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + }); + + it('should send the correct media type', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].mediaType).to.equal(VIDEO) + expect(request.data.bids[1].mediaType).to.equal(BANNER) + }); + + it('should respect syncEnabled option', function() { + config.setConfig({ + userSync: { + syncEnabled: false, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); + }); + + it('should respect "iframe" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); + }); + + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'iframe'); + }); + + it('should send the pixel user sync param if userSync is enabled and no "iframe" or "all" configs are present', function () { + config.resetConfig(); + config.setConfig({ + userSync: { + syncEnabled: true, + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'pixel'); + }); + + it('should respect total exclusion', function() { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: [spec.code], + filter: 'exclude' + }, + iframe: { + bidders: [spec.code], + filter: 'exclude' + } + } + } + }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('cs_method'); + }); + + it('should have us_privacy param if usPrivacy is available in the bidRequest', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('us_privacy', '1YNN'); + }); + + it('should have an empty us_privacy param if usPrivacy is missing in the bidRequest', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('us_privacy'); + }); + + it('should not send the gdpr param if gdprApplies is false in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: false}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.not.have.property('gdpr'); + expect(request.data.params).to.not.have.property('gdpr_consent'); + }); + + it('should send the gdpr param if gdprApplies is true in the bidRequest', function () { + const bidderRequestWithGDPR = Object.assign({gdprConsent: {gdprApplies: true, consentString: 'test-consent-string'}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGDPR); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gdpr', true); + expect(request.data.params).to.have.property('gdpr_consent', 'test-consent-string'); + }); + + it('should have schain param if it is available in the bidRequest', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [{ asi: 'indirectseller.com', sid: '00001', hp: 1 }], + }; + bidRequests[0].schain = schain; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); + }); + + it('should set flooPrice to getFloor.floor value if it is greater than params.floorPrice', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 3.32 + } + } + bid.params.floorPrice = 0.64; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 3.32); + }); + + it('should set floorPrice to params.floorPrice value if it is greater than getFloor.floor', function() { + const bid = utils.deepClone(bidRequests[0]); + bid.getFloor = () => { + return { + currency: 'USD', + floor: 0.8 + } + } + bid.params.floorPrice = 1.5; + const request = spec.buildRequests([bid], bidderRequest); + expect(request.data.bids[0]).to.be.an('object'); + expect(request.data.bids[0]).to.have.property('floorPrice', 1.5); + }); + }); + + describe('interpretResponse', function () { + const response = { + params: { + currency: 'USD', + netRevenue: true, + }, + bids: [{ + cpm: 12.5, + vastXml: '', + width: 640, + height: 480, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: VIDEO + }, + { + cpm: 12.5, + ad: '""', + width: 300, + height: 250, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + mediaType: BANNER + }] + }; + + const expectedVideoResponse = { + cpm: 12.5, + creativeId: '21e12606d47ba7', + currency: 'USD', + height: 480, + mediaType: VIDEO, + meta: { + mediaType: VIDEO, + advertiserDomains: ['abc.com'] + }, + netRevenue: true, + nurl: 'http://example.com/win/1234', + requestId: '21e12606d47ba7', + ttl: TTL, + width: 640, + vastXml: '' + }; + + const expectedBannerResponse = { + cpm: 12.5, + creativeId: '21e12606d47ba7', + currency: 'USD', + height: 480, + mediaType: BANNER, + meta: { + mediaType: BANNER, + advertiserDomains: ['abc.com'] + }, + netRevenue: true, + nurl: 'http://example.com/win/1234', + requestId: '21e12606d47ba7', + ttl: TTL, + width: 640, + ad: '""' + }; + + it('should get correct bid response', function () { + const result = spec.interpretResponse({ body: response }); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + }); + + it('video type should have vastXml key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[0].vastXml).to.equal(expectedVideoResponse.vastXml) + }); + + it('banner type should have ad key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[1].ad).to.equal(expectedBannerResponse.ad) + }); + }) + + describe('getUserSyncs', function() { + const imageSyncResponse = { + body: { + params: { + userSyncPixels: [ + 'https://image-sync-url.test/1', + 'https://image-sync-url.test/2', + 'https://image-sync-url.test/3' + ] + } + } + }; + + const iframeSyncResponse = { + body: { + params: { + userSyncURL: 'https://iframe-sync-url.test' + } + } + }; + + it('should register all img urls from the response', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true }, [imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should register the iframe url from the response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [iframeSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + } + ]); + }); + + it('should register both image and iframe urls from the responses', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [iframeSyncResponse, imageSyncResponse]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://iframe-sync-url.test' + }, + { + type: 'image', + url: 'https://image-sync-url.test/1' + }, + { + type: 'image', + url: 'https://image-sync-url.test/2' + }, + { + type: 'image', + url: 'https://image-sync-url.test/3' + } + ]); + }); + + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); + }); + }) + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should trigger pixel if bid nurl', function() { + const bid = { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [['640', '480']], + 'nurl': 'http://example.com/win/1234', + 'params': { + 'org': 'test-publisher-id' + } + }; + + spec.onBidWon(bid); + expect(utils.triggerPixel.callCount).to.equal(1) + }) + }) +}); diff --git a/test/spec/modules/lassoBidAdapter_spec.js b/test/spec/modules/lassoBidAdapter_spec.js new file mode 100644 index 00000000000..5825927a931 --- /dev/null +++ b/test/spec/modules/lassoBidAdapter_spec.js @@ -0,0 +1,177 @@ +import { expect } from 'chai'; +import { spec } from 'modules/lassoBidAdapter.js'; +import { server } from '../../mocks/xhr'; + +const ENDPOINT_URL = 'https://trc.lhmos.com/prebid'; + +const bid = { + bidder: 'lasso', + params: { + adUnitId: 123456 + }, + auctionStart: Date.now(), + adUnitCode: 'adunit-code', + auctionId: 'cfa6f46d-4584-46e1-9c00-54769abb51e3', + bidderRequestId: 'a123b456c789d', + bidId: '123a456b789', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + sizes: [[300, 250]], + src: 'client', + transactionId: '26740296-0111-4b7a-80df-7196823026f4' +}; + +const bidderRequest = { + auctionId: 'cfa6f46d-4584-46e1-9c00-54769abb51e3', + auctionStart: Date.now(), + start: Date.now(), + biddeCode: 'lasso', + bidderRequestId: 'a123b456c789d', + bids: [bid], + timeout: 10000 +}; + +describe('lassoBidAdapter', function () { + describe('All needed functions are available', function() { + it(`isBidRequestValid is present and type function`, function () { + expect(spec.isBidRequestValid).to.exist.and.to.be.a('function') + }); + + it(`buildRequests is present and type function`, function () { + expect(spec.buildRequests).to.exist.and.to.be.a('function') + }); + + it(`interpretResponse is present and type function`, function () { + expect(spec.interpretResponse).to.exist.and.to.be.a('function') + }); + + it(`onTimeout is present and type function`, function () { + expect(spec.onTimeout).to.exist.and.to.be.a('function') + }); + + it(`onBidWon is present and type function`, function () { + expect(spec.onBidWon).to.exist.and.to.be.a('function') + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return true when there are extra params', function () { + const bid = Object.assign({}, bid, { + params: { + adUnitId: 123456, + zone: 1, + publisher: 'test' + } + }) + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when there are no params', function () { + const invalidBid = { ...bid }; + delete invalidBid.params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const validBidRequests = spec.buildRequests([bid], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + + const bidRequest = validBidRequests[0]; + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + }); + + describe('interpretResponse', function () { + let serverResponse = { + body: { + bidid: '123456789', + id: '33302780340222111', + bid: { + price: 1, + w: 728, + h: 90, + crid: 123456, + ad: '', + mediaType: 'banner' + }, + meta: { + cat: ['1', '2', '3', '4'], + advertiserDomains: ['lassomarketing.io'], + advertiserName: 'Lasso' + }, + cur: 'USD', + netRevenue: false, + ttl: 300, + } + }; + + it('should get the correct bid response', function () { + let expectedResponse = { + requestId: '123456789', + cpm: 1, + currency: 'USD', + width: 728, + height: 90, + creativeId: 123456, + netRevenue: false, + ttl: 300, + ad: '', + mediaType: 'banner', + meta: { + secondaryCatIds: ['1', '2', '3', '4'], + advertiserDomains: ['lassomarketing.io'], + advertiserName: 'Lasso', + mediaType: 'banner' + } + }; + let result = spec.interpretResponse(serverResponse); + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)); + }); + }); + + describe('onTimeout', () => { + it('should send timeout', () => { + const timeoutData = { + bidder: 'lasso', + auctionId: 'cfa6f46d-4584-46e1-9c00-54769abb51e3', + dUnitCode: 'adunit-code', + bidId: '123a456b789', + params: { + adUnitId: 123456, + }, + timeout: 3000 + }; + spec.onTimeout(timeoutData); + + expect(server.requests[0].method).to.equal('POST'); + expect(server.requests[0].url).to.equal(ENDPOINT_URL + '/timeout'); + expect(JSON.parse(server.requests[0].requestBody)).to.deep.equal(timeoutData); + }); + }); + + describe('onBidWon', () => { + it('should send bid won request', () => { + spec.onBidWon(bid); + + expect(server.requests[0].method).to.equal('POST'); + expect(server.requests[0].url).to.equal(ENDPOINT_URL + '/won'); + expect(JSON.parse(server.requests[0].requestBody)).to.deep.equal(bid); + }); + }); +}); diff --git a/test/spec/modules/lifestreetBidAdapter_spec.js b/test/spec/modules/lifestreetBidAdapter_spec.js new file mode 100644 index 00000000000..d66727da644 --- /dev/null +++ b/test/spec/modules/lifestreetBidAdapter_spec.js @@ -0,0 +1,232 @@ +import { expect } from 'chai'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { spec } from 'modules/lifestreetBidAdapter.js'; + +describe('lifestreetBidAdapter', function() { + let bidRequests; + let videoBidRequests; + let bidResponses; + let videoBidResponses; + beforeEach(function() { + bidRequests = [ + { + bidder: 'lifestreet', + params: { + slot: 'slot166704', + adkey: '78c', + ad_size: '160x600' + }, + mediaTypes: { + banner: { + sizes: [ + [160, 600], + [300, 600] + ] + } + }, + sizes: [ + [160, 600], + [300, 600] + ] + } + ]; + + bidResponses = { + body: { + cpm: 0.1, + netRevenue: true, + content_type: 'display_flash', + width: 160, + currency: 'USD', + ttl: 86400, + content: '', 'price': 0.8, @@ -81,6 +83,15 @@ const REQUEST = { } }; +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + describe('VidazooBidAdapter', function () { describe('validtae spec', function () { it('exists and is a function', function () { @@ -137,12 +148,17 @@ describe('VidazooBidAdapter', function () { describe('build requests', function () { let sandbox; before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + vidazoo: { + storageAllowed: true + } + }; sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1000); }); it('should build request for each size', function () { - const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.referer); + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); expect(requests[0]).to.deep.equal({ @@ -154,6 +170,7 @@ describe('VidazooBidAdapter', function () { usPrivacy: 'consent_string', sizes: ['300x250', '300x600'], url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', cb: 1000, bidFloor: 0.1, bidId: '2d52001cabd527', @@ -166,6 +183,7 @@ describe('VidazooBidAdapter', function () { prebidVersion: version, schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, + uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', } @@ -173,24 +191,33 @@ describe('VidazooBidAdapter', function () { }); after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; sandbox.restore(); }); }); describe('getUserSyncs', function () { it('should have valid user sync with iframeEnabled', function () { - const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://prebid.cootlogix.com/api/sync/iframe/?gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://sync.cootlogix.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' }]); }); it('should have valid user sync with pixelEnabled', function () { - const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://prebid.cootlogix.com/api/sync/image/?gdpr=0&gdpr_consent=&us_privacy=', + 'url': 'https://sync.cootlogix.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', 'type': 'image' }]); }) @@ -203,12 +230,12 @@ describe('VidazooBidAdapter', function () { }); it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({ price: 1, ad: '' }); + const responses = adapter.interpretResponse({price: 1, ad: ''}); expect(responses).to.be.empty; }); it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); expect(responses).to.be.empty; }); @@ -247,11 +274,14 @@ describe('VidazooBidAdapter', function () { const userId = (function () { switch (idSystemProvider) { - case 'digitrustid': return { data: { id: id } }; - case 'lipb': return { lipbid: id }; - case 'parrableId': return { eid: id }; - case 'id5id': return { uid: id }; - default: return id; + case 'lipb': + return {lipbid: id}; + case 'parrableId': + return {eid: id}; + case 'id5id': + return {uid: id}; + default: + return id; } })(); @@ -268,18 +298,18 @@ describe('VidazooBidAdapter', function () { describe('alternate param names extractors', function () { it('should return undefined when param not supported', function () { - const cid = extractCID({ 'c_id': '1' }); - const pid = extractPID({ 'p_id': '1' }); - const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); expect(cid).to.be.undefined; expect(pid).to.be.undefined; expect(subDomain).to.be.undefined; }); it('should return value when param supported', function () { - const cid = extractCID({ 'cID': '1' }); - const pid = extractPID({ 'Pid': '2' }); - const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); expect(cid).to.be.equal('1'); expect(pid).to.be.equal('2'); expect(subDomain).to.be.equal('prebid'); @@ -287,6 +317,16 @@ describe('VidazooBidAdapter', function () { }); describe('vidazoo session id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + vidazoo: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); it('should get undefined vidazoo session id', function () { const sessionId = getVidazooSessionId(); expect(sessionId).to.be.empty; @@ -301,6 +341,16 @@ describe('VidazooBidAdapter', function () { }); describe('deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + vidazoo: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); const key = 'myDealKey'; it('should get the next deal id', function () { @@ -320,9 +370,21 @@ describe('VidazooBidAdapter', function () { }); describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + vidazoo: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); const key = 'myKey'; let uniqueDealId; - uniqueDealId = getUniqueDealId(key); + beforeEach(() => { + uniqueDealId = getUniqueDealId(key, 0); + }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past @@ -333,13 +395,26 @@ describe('VidazooBidAdapter', function () { }, 200); }); - it('should get new unique deal id on expiration', function () { - const current = getUniqueDealId(key, 100); - expect(current).to.not.be.equal(uniqueDealId); + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) }); }); describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + vidazoo: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); it('should get value from storage with create param', function () { const now = Date.now(); const clock = useFakeTimers({ @@ -347,7 +422,7 @@ describe('VidazooBidAdapter', function () { now }); setStorageItem('myKey', 2020); - const { value, created } = getStorageItem('myKey'); + const {value, created} = getStorageItem('myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -363,8 +438,8 @@ describe('VidazooBidAdapter', function () { }); it('should parse JSON value', function () { - const data = JSON.stringify({ event: 'send' }); - const { event } = tryParseJSON(data); + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); expect(event).to.be.equal('send'); }); diff --git a/test/spec/modules/videonowBidAdapter_spec.js b/test/spec/modules/videonowBidAdapter_spec.js new file mode 100644 index 00000000000..c9eb5ba0bbf --- /dev/null +++ b/test/spec/modules/videonowBidAdapter_spec.js @@ -0,0 +1,80 @@ +import {expect} from 'chai'; +import {spec} from 'modules/videonowBidAdapter'; + +describe('videonowBidAdapter', function () { + it('minimal params', function () { + expect(spec.isBidRequestValid({ + bidder: 'videonow', + params: { + pId: 'advDesktopBillboard' + }})).to.equal(true) + }) + + it('minimal params no placementId', function () { + expect(spec.isBidRequestValid({ + bidder: 'videonow', + params: { + currency: `GBP` + }})).to.equal(false) + }) + + it('generated_params common case', function () { + const bidRequestData = [{ + bidId: 'bid1234', + bidder: 'videonow', + params: { + pId: 'advDesktopBillboard', + currency: `GBP` + }, + sizes: [[240, 400]] + }]; + + const request = spec.buildRequests(bidRequestData); + const req_data = request[0].data; + + expect(req_data.places[0].id).to.equal(`bid1234`) + expect(req_data.places[0].placementId).to.equal(`advDesktopBillboard`) + expect(req_data.settings.currency).to.equal(`GBP`) + expect(req_data.places[0].sizes[0][0]).to.equal(240); + expect(req_data.places[0].sizes[0][1]).to.equal(400); + }); + + it('response_params common case', function () { + const bidRequestData = { + data: { + bidId: 'bid1234' + } + }; + + const serverResponse = { + body: { + bids: [ + { + 'displayCode': 'test html', + 'id': '123456', + 'cpm': 375, + 'currency': 'RUB', + 'placementId': 'profileName', + 'codeType': 'js', + 'size': { + 'width': 640, + 'height': 480 + } + } + ] + } + }; + + const bids = spec.interpretResponse(serverResponse, bidRequestData); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.requestId).to.equal('123456') + expect(bid.cpm).to.equal(375); + expect(bid.currency).to.equal('RUB'); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.ad).to.equal('test html'); + expect(bid.creativeId).to.equal(`123456`) + expect(bid.netRevenue).to.equal(true); + }); +}) diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 8aa127faef2..61b7f2fad7d 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -95,7 +95,8 @@ describe('vidoomyBidAdapter', function() { refererInfo: { numIframes: 0, reachedTop: true, - referer: 'http://example.com', + domain: 'example.com', + page: 'http://example.com', stack: ['http://example.com'] } }; diff --git a/test/spec/modules/viewability_spec.js b/test/spec/modules/viewability_spec.js deleted file mode 100644 index ab2753daf53..00000000000 --- a/test/spec/modules/viewability_spec.js +++ /dev/null @@ -1,280 +0,0 @@ -import { expect } from 'chai'; -import * as sinon from 'sinon'; -import * as utils from 'src/utils.js'; -import * as viewability from 'modules/viewability.js'; - -describe('viewability test', () => { - describe('start measurement', () => { - let sandbox; - let intersectionObserverStub; - let setTimeoutStub; - let observeCalled; - let unobserveCalled; - let ti = 1; - beforeEach(() => { - observeCalled = false; - unobserveCalled = false; - sandbox = sinon.sandbox.create(); - - let fakeIntersectionObserver = (stateChange, options) => { - return { - observe: (element) => { - observeCalled = true; - stateChange([{ isIntersecting: true }]); - }, - unobserve: (element) => { - unobserveCalled = true; - }, - }; - }; - - intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver); - setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => { - callback(); - return ti++; - }); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should trigger appropriate callbacks', () => { - viewability.startMeasurement('0', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); - - sinon.assert.called(intersectionObserverStub); - sinon.assert.called(setTimeoutStub); - expect(observeCalled).to.equal(true); - expect(unobserveCalled).to.equal(true); - }); - - it('should trigger img tracker', () => { - let triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); - viewability.startMeasurement('1', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); - expect(triggerPixelSpy.callCount).to.equal(1); - }); - - it('should trigger js tracker', () => { - let insertHtmlIntoIframeSpy = sandbox.spy(utils, ['insertHtmlIntoIframe']); - viewability.startMeasurement('2', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 }); - expect(insertHtmlIntoIframeSpy.callCount).to.equal(1); - }); - - it('should trigger callback tracker', () => { - let callbackFired = false; - viewability.startMeasurement('3', {}, { method: 'callback', value: () => { callbackFired = true; } }, { inViewThreshold: 0.5, timeInView: 1000 }); - expect(callbackFired).to.equal(true); - }); - - it('should check for vid uniqueness', () => { - let logWarnSpy = sandbox.spy(utils, 'logWarn'); - viewability.startMeasurement('4', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 }); - expect(logWarnSpy.callCount).to.equal(0); - - viewability.startMeasurement('4', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 }); - expect(logWarnSpy.callCount).to.equal(1); - expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: must provide an unregistered vid`, '4')).to.equal(true); - }); - - it('should check for valid criteria', () => { - let logWarnSpy = sandbox.spy(utils, 'logWarn'); - viewability.startMeasurement('5', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { timeInView: 1000 }); - expect(logWarnSpy.callCount).to.equal(1); - expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: missing criteria`, { timeInView: 1000 })).to.equal(true); - }); - - it('should check for valid tracker', () => { - let logWarnSpy = sandbox.spy(utils, 'logWarn'); - viewability.startMeasurement('6', {}, { method: 'callback', value: 'string' }, { inViewThreshold: 0.5, timeInView: 1000 }); - expect(logWarnSpy.callCount).to.equal(1); - expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: invalid tracker`, { method: 'callback', value: 'string' })).to.equal(true); - }); - - it('should check if element provided', () => { - let logWarnSpy = sandbox.spy(utils, 'logWarn'); - viewability.startMeasurement('7', undefined, { method: 'js', value: 'http://my.tracker/123.js' }, { timeInView: 1000 }); - expect(logWarnSpy.callCount).to.equal(1); - expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: no html element provided`)).to.equal(true); - }); - }); - - describe('stop measurement', () => { - let sandbox; - let intersectionObserverStub; - let setTimeoutStub; - let clearTimeoutStub; - let observeCalled; - let unobserveCalled; - let stateChangeBackup; - let ti = 1; - beforeEach(() => { - observeCalled = false; - unobserveCalled = false; - sandbox = sinon.sandbox.create(); - - let fakeIntersectionObserver = (stateChange, options) => { - return { - observe: (element) => { - stateChangeBackup = stateChange; - observeCalled = true; - stateChange([{ isIntersecting: true }]); - }, - unobserve: (element) => { - unobserveCalled = true; - }, - }; - }; - - intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver); - setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => { - // skipping the callback - return ti++; - }); - clearTimeoutStub = sandbox.stub(window, 'clearTimeout').callsFake((timeoutId) => { }); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should clear the timeout', () => { - viewability.startMeasurement('10', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); - stateChangeBackup([{ isIntersecting: false }]); - sinon.assert.called(intersectionObserverStub); - sinon.assert.called(setTimeoutStub); - sinon.assert.called(clearTimeoutStub); - expect(observeCalled).to.equal(true); - }); - - it('should unobserve', () => { - viewability.startMeasurement('11', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); - sinon.assert.called(intersectionObserverStub); - sinon.assert.called(setTimeoutStub); - expect(observeCalled).to.equal(true); - expect(unobserveCalled).to.equal(false); - - viewability.stopMeasurement('11'); - expect(unobserveCalled).to.equal(true); - sinon.assert.called(clearTimeoutStub); - }); - - it('should check for vid existence', () => { - let logWarnSpy = sandbox.spy(utils, 'logWarn'); - viewability.stopMeasurement('100'); - expect(logWarnSpy.callCount).to.equal(1); - expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: must provide a registered vid`, '100')).to.equal(true); - }); - }); - - describe('handle creative messages', () => { - let sandbox; - let intersectionObserverStub; - let setTimeoutStub; - let observeCalled; - let unobserveCalled; - let ti = 1; - let getElementsByTagStub; - let getElementByIdStub; - - let fakeContentWindow = {}; - beforeEach(() => { - observeCalled = false; - unobserveCalled = false; - sandbox = sinon.sandbox.create(); - - let fakeIntersectionObserver = (stateChange, options) => { - return { - observe: (element) => { - observeCalled = true; - stateChange([{ isIntersecting: true }]); - }, - unobserve: (element) => { - unobserveCalled = true; - }, - }; - }; - - intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver); - setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => { - callback(); - return ti++; - }); - - getElementsByTagStub = sandbox.stub(document, 'getElementsByTagName').callsFake((tagName) => { - return [{ - contentWindow: fakeContentWindow, - }]; - }); - getElementByIdStub = sandbox.stub(document, 'getElementById').callsFake((id) => { - return {}; - }); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should find element by contentWindow', () => { - let viewabilityRecord = { - vid: 1000, - tracker: { - value: 'http://my.tracker/123', - method: 'img', - }, - criteria: { inViewThreshold: 0.5, timeInView: 1000 }, - message: 'Prebid Viewability', - action: 'startMeasurement', - }; - let data = JSON.stringify(viewabilityRecord); - - viewability.receiveMessage({ - data: data, - source: fakeContentWindow, - }); - - sinon.assert.called(getElementsByTagStub); - sinon.assert.called(intersectionObserverStub); - sinon.assert.called(setTimeoutStub); - expect(observeCalled).to.equal(true); - expect(unobserveCalled).to.equal(true); - }); - - it('should find element by id', () => { - let viewabilityRecord = { - vid: 1001, - tracker: { - value: 'http://my.tracker/123', - method: 'img', - }, - criteria: { inViewThreshold: 0.5, timeInView: 1000 }, - message: 'Prebid Viewability', - action: 'startMeasurement', - elementId: '1', - }; - let data = JSON.stringify(viewabilityRecord); - viewability.receiveMessage({ - data: data, - }); - - sinon.assert.called(getElementByIdStub); - sinon.assert.called(intersectionObserverStub); - sinon.assert.called(setTimeoutStub); - expect(observeCalled).to.equal(true); - expect(unobserveCalled).to.equal(true); - }); - - it('should stop measurement', () => { - let viewabilityRecord = { - vid: 1001, - message: 'Prebid Viewability', - action: 'stopMeasurement', - }; - let data = JSON.stringify(viewabilityRecord); - viewability.receiveMessage({ - data: data, - }); - - expect(unobserveCalled).to.equal(true); - }); - }); -}); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index 4aaaf996f58..b8a66e7c3b9 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -85,10 +85,10 @@ describe('VisxAdapter', function () { const bidderRequest = { timeout: 3000, refererInfo: { - referer: 'https://example.com' + page: 'https://example.com' } }; - const referrer = bidderRequest.refererInfo.referer; + const referrer = bidderRequest.refererInfo.page; const schainObject = { ver: '1.0', nodes: [ @@ -425,10 +425,10 @@ describe('VisxAdapter', function () { const bidderRequest = { timeout: 3000, refererInfo: { - referer: 'https://example.com' + page: 'https://example.com' } }; - const referrer = bidderRequest.refererInfo.referer; + const referrer = bidderRequest.refererInfo.page; const bidRequests = [ { 'bidder': 'visx', @@ -489,10 +489,10 @@ describe('VisxAdapter', function () { const bidderRequest = { timeout: 3000, refererInfo: { - referer: 'https://example.com' + page: 'https://example.com' } }; - const referrer = bidderRequest.refererInfo.referer; + const referrer = bidderRequest.refererInfo.page; const bidRequests = [ { 'bidder': 'visx', diff --git a/test/spec/modules/voxBidAdapter_spec.js b/test/spec/modules/voxBidAdapter_spec.js index 6906c7dbba4..923b0465e6c 100644 --- a/test/spec/modules/voxBidAdapter_spec.js +++ b/test/spec/modules/voxBidAdapter_spec.js @@ -15,7 +15,7 @@ function getSlotConfigs(mediaTypes, params) { describe('VOX Adapter', function() { const PLACE_ID = '5af45ad34d506ee7acad0c26'; const bidderRequest = { - refererInfo: { referer: 'referer' } + refererInfo: { page: 'referer' } } const bannerMandatoryParams = { placementId: PLACE_ID, diff --git a/test/spec/modules/vrtcalBidAdapter_spec.js b/test/spec/modules/vrtcalBidAdapter_spec.js index 66440130860..2f9f1b96142 100644 --- a/test/spec/modules/vrtcalBidAdapter_spec.js +++ b/test/spec/modules/vrtcalBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai' import { spec } from 'modules/vrtcalBidAdapter' import { newBidder } from 'src/adapters/bidderFactory' +import { config } from 'src/config.js'; describe('vrtcalBidAdapter', function () { const adapter = newBidder(spec) @@ -50,6 +51,22 @@ describe('vrtcalBidAdapter', function () { request = spec.buildRequests(bidRequests); expect(request[0].data).to.match(/"bidfloor":0.55/); }); + + it('pass GDPR,CCPA, and COPPA indicators/consent strings with the request when present', function () { + bidRequests[0].gdprConsent = {consentString: 'gdpr-consent-string', gdprApplies: true}; + bidRequests[0].uspConsent = 'ccpa-consent-string'; + config.setConfig({ coppa: false }); + + request = spec.buildRequests(bidRequests); + expect(request[0].data).to.match(/"user":{"ext":{"consent":"gdpr-consent-string"}}/); + expect(request[0].data).to.match(/"regs":{"coppa":0,"ext":{"gdpr":1,"us_privacy":"ccpa-consent-string"}}/); + }); + + it('pass bidder timeout/tmax with the request', function () { + config.setConfig({ bidderTimeout: 435 }); + request = spec.buildRequests(bidRequests); + expect(request[0].data).to.match(/"tmax":435/); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/weboramaRtdProvider_spec.js b/test/spec/modules/weboramaRtdProvider_spec.js index 4dff3db5c18..523406dc492 100644 --- a/test/spec/modules/weboramaRtdProvider_spec.js +++ b/test/spec/modules/weboramaRtdProvider_spec.js @@ -88,6 +88,10 @@ describe('weboramaRtdProvider', function() { }; const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -132,7 +136,7 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ inventory: data }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ site: { ext: { data: data @@ -195,6 +199,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -252,7 +260,7 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[1].params).to.be.undefined; expect(adUnit.bids[2].params.keywords).to.deep.equal(data); expect(adUnit.bids[3].params).to.be.undefined; - expect(adUnit.bids[4].ortb2).to.be.undefined; + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; }); expect(onDataResponse).to.deep.equal({ @@ -304,6 +312,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -407,6 +419,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -511,7 +527,7 @@ describe('weboramaRtdProvider', function() { } }); - expect(adUnit.bids[4].ortb2).to.be.undefined; + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; }); }); }); @@ -545,6 +561,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -649,7 +669,7 @@ describe('weboramaRtdProvider', function() { } }); - expect(adUnit.bids[4].ortb2).to.be.undefined; + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; }); }); }); @@ -680,6 +700,9 @@ describe('weboramaRtdProvider', function() { }; const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {} + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -739,6 +762,10 @@ describe('weboramaRtdProvider', function() { }; const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -812,7 +839,7 @@ describe('weboramaRtdProvider', function() { baz: 'bam', } }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ site: { ext: { data: data @@ -845,6 +872,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -888,7 +919,7 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ inventory: defaultProfile }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ site: { ext: { data: defaultProfile @@ -941,6 +972,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -1003,7 +1038,7 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[3].params).to.deep.equal({ inventory: data }); - expect(adUnit.bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ site: { ext: { data: data @@ -1062,6 +1097,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -1097,7 +1136,7 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ visitor: data }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ user: { ext: { data: data @@ -1168,6 +1207,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -1216,8 +1259,8 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[1].params).to.be.undefined; expect(adUnit.bids[2].params.keywords).to.deep.equal(data); expect(adUnit.bids[3].params).to.be.undefined; - expect(adUnit.bids[4].ortb2).to.be.undefined; }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; expect(onDataResponse).to.deep.equal({ data: data, @@ -1276,6 +1319,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -1323,8 +1370,8 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[0].params).to.be.undefined; expect(adUnit.bids[1].params).to.be.undefined; expect(adUnit.bids[3].params).to.be.undefined; - expect(adUnit.bids[4].ortb2).to.be.undefined; }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); expect(reqBidsConfigObj.adUnits[1].bids[2].params).to.be.undefined; @@ -1379,6 +1426,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -1474,8 +1525,8 @@ describe('weboramaRtdProvider', function() { baz: 'bam' } }); - expect(adUnit.bids[4].ortb2).to.be.undefined; }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; }); }); }); @@ -1517,6 +1568,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -1612,8 +1667,8 @@ describe('weboramaRtdProvider', function() { baz: 'bam' } }); - expect(adUnit.bids[4].ortb2).to.be.undefined; }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; }); }); }); @@ -1712,6 +1767,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -1777,7 +1836,7 @@ describe('weboramaRtdProvider', function() { webo_audiences: ['baz'], } }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ user: { ext: { data: data @@ -1804,6 +1863,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -1839,7 +1902,7 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ visitor: defaultProfile }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ user: { ext: { data: defaultProfile @@ -1873,6 +1936,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -1908,7 +1975,7 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ visitor: defaultProfile }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ user: { ext: { data: defaultProfile @@ -1970,6 +2037,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -2024,7 +2095,7 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[3].params).to.deep.equal({ visitor: data }); - expect(adUnit.bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ user: { ext: { data: data @@ -2082,6 +2153,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -2117,7 +2192,7 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ inventory: data, }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ site: { ext: { data: data, @@ -2187,6 +2262,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -2235,8 +2314,8 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[1].params).to.be.undefined; expect(adUnit.bids[2].params.keywords).to.deep.equal(data); expect(adUnit.bids[3].params).to.be.undefined; - expect(adUnit.bids[4].ortb2).to.be.undefined; }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; expect(onDataResponse).to.deep.equal({ data: data, @@ -2294,6 +2373,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -2341,8 +2424,8 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[0].params).to.be.undefined; expect(adUnit.bids[1].params).to.be.undefined; expect(adUnit.bids[3].params).to.be.undefined; - expect(adUnit.bids[4].ortb2).to.be.undefined; }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(data); expect(reqBidsConfigObj.adUnits[1].bids[2].params).to.be.undefined; @@ -2396,6 +2479,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -2491,8 +2578,8 @@ describe('weboramaRtdProvider', function() { baz: 'bam' } }); - expect(adUnit.bids[4].ortb2).to.be.undefined; }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; }); }); }); @@ -2533,6 +2620,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -2628,8 +2719,8 @@ describe('weboramaRtdProvider', function() { baz: 'bam' } }); - expect(adUnit.bids[4].ortb2).to.be.undefined; }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.be.undefined; }); }); }); @@ -2667,6 +2758,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -2726,6 +2821,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -2791,7 +2890,7 @@ describe('weboramaRtdProvider', function() { baz: 'bam', } }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ site: { ext: { data: data, @@ -2817,6 +2916,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -2852,7 +2955,7 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ inventory: defaultProfile, }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ site: { ext: { data: defaultProfile, @@ -2885,6 +2988,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode = 'adunit1'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode, bids: [{ @@ -2920,7 +3027,7 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ inventory: defaultProfile, }); - expect(reqBidsConfigObj.adUnits[0].bids[4].ortb2).to.deep.equal({ + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ site: { ext: { data: defaultProfile, @@ -2981,6 +3088,10 @@ describe('weboramaRtdProvider', function() { const adUnitCode1 = 'adunit1'; const adUnitCode2 = 'adunit2'; const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, adUnits: [{ code: adUnitCode1, bids: [{ @@ -3035,13 +3146,13 @@ describe('weboramaRtdProvider', function() { expect(adUnit.bids[3].params).to.deep.equal({ inventory: data, }); - expect(adUnit.bids[4].ortb2).to.deep.equal({ - site: { - ext: { - data: data, - }, + }); + expect(reqBidsConfigObj.ortb2Fragments.bidder.other).to.deep.equal({ + site: { + ext: { + data: data, }, - }); + }, }); expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ diff --git a/test/spec/modules/winrBidAdapter_spec.js b/test/spec/modules/winrBidAdapter_spec.js index 03e441df727..174f600fa06 100644 --- a/test/spec/modules/winrBidAdapter_spec.js +++ b/test/spec/modules/winrBidAdapter_spec.js @@ -434,7 +434,7 @@ describe('WinrAdapter', function () { const bidRequest = Object.assign({}, bidRequests[0]) const bidderRequest = { refererInfo: { - referer: 'https://example.com/page.html', + topmostLocation: 'https://example.com/page.html', reachedTop: true, numIframes: 2, stack: [ @@ -563,11 +563,7 @@ describe('WinrAdapter', function () { uid2: { id: 'sample-uid2-value' }, criteoId: 'sample-criteo-userid', netId: 'sample-netId-userid', - idl_env: 'sample-idl-userid', - flocId: { - id: 'sample-flocid-value', - version: 'chrome.1.0' - } + idl_env: 'sample-idl-userid' } }); @@ -584,11 +580,6 @@ describe('WinrAdapter', function () { id: 'sample-criteo-userid', }); - expect(payload.eids).to.deep.include({ - source: 'chrome.com', - id: 'sample-flocid-value' - }); - expect(payload.eids).to.deep.include({ source: 'netid.de', id: 'sample-netId-userid', diff --git a/test/spec/modules/xeBidAdapter_spec.js b/test/spec/modules/xeBidAdapter_spec.js new file mode 100644 index 00000000000..dcd82683221 --- /dev/null +++ b/test/spec/modules/xeBidAdapter_spec.js @@ -0,0 +1,450 @@ +import { expect } from 'chai'; +import { config } from 'src/config.js'; +import { spec, getBidFloor } from 'modules/xeBidAdapter.js'; +import { deepClone } from 'src/utils'; +import { createEidsArray } from 'modules/userId/eids.js'; + +const ENDPOINT = 'https://pbjs.xe.works/bid'; + +const defaultRequest = { + adUnitCode: 'test', + bidId: '1', + requestId: 'qwerty', + auctionId: 'auctionId', + transactionId: 'tr1', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'xe', + params: { + env: 'xe', + placement: 'test-banner', + ext: {} + }, + bidRequestsCount: 1 +}; + +const defaultRequestVideo = deepClone(defaultRequest); +defaultRequestVideo.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } +}; + +describe('isBidRequestValid', function () { + it('should return false when request params is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required env param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.env; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required placement param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.placement; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when video.playerSize is missing', function () { + const invalidRequest = deepClone(defaultRequestVideo); + delete invalidRequest.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); + }); +}); + +describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); + }); + + it('should send request with correct structure', function () { + const request = spec.buildRequests([defaultRequest], {}); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT); + expect(request.options).to.have.property('contentType').and.to.equal('application/json'); + expect(request).to.have.property('data'); + }); + + it('should build basic request structure', function () { + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.auctionId); + expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.transactionId); + expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); + expect(request).to.have.property('bc').and.to.equal(1); + expect(request).to.have.property('floor').and.to.equal(null); + expect(request).to.have.property('banner').and.to.deep.equal({ sizes: [[300, 250], [300, 200]] }); + expect(request).to.have.property('gdprApplies').and.to.equal(0); + expect(request).to.have.property('consentString').and.to.equal(''); + expect(request).to.have.property('userEids').and.to.deep.equal([]); + expect(request).to.have.property('usPrivacy').and.to.equal(''); + expect(request).to.have.property('coppa').and.to.equal(0); + expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); + expect(request).to.have.property('ext').and.to.deep.equal({}); + expect(request).to.have.property('env').and.to.deep.equal({ + env: 'xe', + placement: 'test-banner' + }); + expect(request).to.have.property('device').and.to.deep.equal({ + ua: navigator.userAgent, + lang: navigator.language + }); + }); + + it('should build request with schain', function () { + const schainRequest = deepClone(defaultRequest); + schainRequest.schain = { + validation: 'strict', + config: { + ver: '1.0' + } + }; + const request = JSON.parse(spec.buildRequests([schainRequest], {}).data)[0]; + expect(request).to.have.property('schain').and.to.deep.equal({ + validation: 'strict', + config: { + ver: '1.0' + } + }); + }); + + it('should build request with location', function () { + const bidderRequest = { + refererInfo: { + page: 'page', + location: 'location', + domain: 'domain', + ref: 'ref', + isAmp: false + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('location'); + const location = request.location; + expect(location).to.have.property('page').and.to.equal('page'); + expect(location).to.have.property('location').and.to.equal('location'); + expect(location).to.have.property('domain').and.to.equal('domain'); + expect(location).to.have.property('ref').and.to.equal('ref'); + expect(location).to.have.property('isAmp').and.to.equal(false); + }); + + it('should build request with ortb2 info', function () { + const ortb2Request = deepClone(defaultRequest); + ortb2Request.ortb2 = { + site: { + name: 'name' + } + }; + const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; + expect(request).to.have.property('ortb2').and.to.deep.equal({ + site: { + name: 'name' + } + }); + }); + + it('should build request with ortb2Imp info', function () { + const ortb2ImpRequest = deepClone(defaultRequest); + ortb2ImpRequest.ortb2Imp = { + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }; + const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; + expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }); + }); + + it('should build request with valid bidfloor', function () { + const bfRequest = deepClone(defaultRequest); + bfRequest.getFloor = () => ({ floor: 5, currency: 'USD' }); + const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; + expect(request).to.have.property('floor').and.to.equal(5); + }); + + it('should build request with gdpr consent data if applies', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'qwerty' + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('gdprApplies').and.equals(1); + expect(request).to.have.property('consentString').and.equals('qwerty'); + }); + + it('should build request with usp consent data if applies', function () { + const bidderRequest = { + uspConsent: '1YA-' + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('usPrivacy').and.equals('1YA-'); + }); + + it('should build request with coppa 1', function () { + config.setConfig({ + coppa: true + }); + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('coppa').and.equals(1); + }); + + it('should build request with extended ids', function () { + const idRequest = deepClone(defaultRequest); + idRequest.userIdAsEids = createEidsArray({ + tdid: 'TTD_ID_FROM_USER_ID_MODULE', + pubcid: 'pubCommonId_FROM_USER_ID_MODULE' + }); + const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; + expect(request).to.have.property('userEids').and.deep.equal([ + { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, + { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } + ]); + }); + + it('should build request with video', function () { + const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; + expect(request).to.have.property('video').and.to.deep.equal({ + playerSize: [640, 480], + context: 'instream', + skipppable: true + }); + expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); + }); +}); + +describe('interpretResponse', function () { + it('should return empty bids', function () { + const serverResponse = { + body: { + data: null + } + }; + + const invalidResponse = spec.interpretResponse(serverResponse, {}); + expect(invalidResponse).to.be.an('array').that.is.empty; + }); + + it('should interpret valid response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + meta: { + advertiserDomains: ['xe.works'] + }, + ext: { + pixels: [ + [ 'iframe', 'surl1' ], + [ 'image', 'surl2' ], + ] + } + }] + } + }; + + const validResponse = spec.interpretResponse(serverResponse, { bidderRequest: defaultRequest }); + const bid = validResponse[0]; + expect(validResponse).to.be.an('array').that.is.not.empty; + expect(bid.requestId).to.equal('qwerty'); + expect(bid.cpm).to.equal(1); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ttl).to.equal(600); + expect(bid.meta).to.deep.equal({ advertiserDomains: ['xe.works'] }); + }); + + it('should interpret valid banner response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + mediaType: 'banner', + creativeId: 'xe-demo-banner', + ad: 'ad', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, { bidderRequest: defaultRequest }); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('banner'); + expect(bid.creativeId).to.equal('xe-demo-banner'); + expect(bid.ad).to.equal('ad'); + }); + + it('should interpret valid video response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 600, + height: 480, + ttl: 600, + mediaType: 'video', + creativeId: 'xe-demo-video', + ad: 'vast-xml', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, { bidderRequest: defaultRequestVideo }); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('xe-demo-video'); + expect(bid.ad).to.equal('vast-xml'); + }); +}); + +describe('getUserSyncs', function () { + it('shoukd handle no params', function () { + const opts = spec.getUserSyncs({}, []); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return empty if sync is not allowed', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should allow iframe sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + [ 'iframe', 'surl1?a=b' ], + [ 'image', 'surl2?a=b' ], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + [ 'iframe', 'surl1?a=b' ], + [ 'image', 'surl2?a=b' ], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync and parse consent params', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + [ 'iframe', 'surl1?a=b' ], + [ 'image', 'surl2?a=b' ], + ] + } + }] + } + }], { + gdprApplies: 1, + consentString: '1YA-' + }); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); + }); +}); + +describe('getBidFloor', function () { + it('should return null when getFloor is not a function', () => { + const bid = { getFloor: 2 }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when getFloor doesnt return an object', () => { + const bid = { getFloor: () => 2 }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when floor is not a number', () => { + const bid = { + getFloor: () => ({ floor: 'string', currency: 'USD' }) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when currency is not USD', () => { + const bid = { + getFloor: () => ({ floor: 5, currency: 'EUR' }) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return floor value when everything is correct', () => { + const bid = { + getFloor: () => ({ floor: 5, currency: 'USD' }) + }; + const result = getBidFloor(bid); + expect(result).to.equal(5); + }); +}); diff --git a/test/spec/modules/yahoosspBidAdapter_spec.js b/test/spec/modules/yahoosspBidAdapter_spec.js index e301218741c..6e52e9c57db 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahoosspBidAdapter_spec.js @@ -16,7 +16,7 @@ const PREBID_VERSION = '$prebid.version$'; const INTEGRATION_METHOD = 'prebid.js'; // Utility functions -const generateBidRequest = ({bidId, pos, adUnitCode, adUnitType, bidOverrideObject, videoContext, pubIdMode}) => { +const generateBidRequest = ({bidId, pos, adUnitCode, adUnitType, bidOverrideObject, videoContext, pubIdMode, ortb2}) => { const bidRequest = { adUnitCode, auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', @@ -30,7 +30,8 @@ const generateBidRequest = ({bidId, pos, adUnitCode, adUnitType, bidOverrideObje bidOverride: bidOverrideObject }, src: 'client', - transactionId: '5b17b67d-7704-4732-8cc9-5b1723e9bcf9' + transactionId: '5b17b67d-7704-4732-8cc9-5b1723e9bcf9', + ortb2 }; const bannerObj = { @@ -71,7 +72,7 @@ const generateBidRequest = ({bidId, pos, adUnitCode, adUnitType, bidOverrideObje return bidRequest; } -let generateBidderRequest = (bidRequestArray, adUnitCode) => { +let generateBidderRequest = (bidRequestArray, adUnitCode, ortb2 = {}) => { const bidderRequest = { adUnitCode: adUnitCode || 'default-adUnitCode', auctionId: 'd4c83a3b-18e4-4208-b98b-63848449c7aa', @@ -80,7 +81,7 @@ let generateBidderRequest = (bidRequestArray, adUnitCode) => { bidderRequestId: '112f1c7c5d399a', bids: bidRequestArray, refererInfo: { - referer: 'https://publisher-test.com', + page: 'https://publisher-test.com', reachedTop: true, isAmp: false, numIframes: 0, @@ -93,12 +94,13 @@ let generateBidderRequest = (bidRequestArray, adUnitCode) => { }, start: new Date().getTime(), timeout: 1000, + ortb2 }; return bidderRequest; }; -const generateBuildRequestMock = ({bidId, pos, adUnitCode, adUnitType, bidOverrideObject, videoContext, pubIdMode}) => { +const generateBuildRequestMock = ({bidId, pos, adUnitCode, adUnitType, bidOverrideObject, videoContext, pubIdMode, ortb2}) => { const bidRequestConfig = { bidId: bidId || DEFAULT_BID_ID, pos: pos || DEFAULT_BID_POS, @@ -106,11 +108,12 @@ const generateBuildRequestMock = ({bidId, pos, adUnitCode, adUnitType, bidOverri adUnitType: adUnitType || DEFAULT_AD_UNIT_TYPE, bidOverrideObject: bidOverrideObject || DEFAULT_PARAMS_BID_OVERRIDE, videoContext: videoContext || DEFAULT_VIDEO_CONTEXT, - pubIdMode: pubIdMode || false + pubIdMode: pubIdMode || false, + ortb2: ortb2 || {} }; const bidRequest = generateBidRequest(bidRequestConfig); const validBidRequests = [bidRequest]; - const bidderRequest = generateBidderRequest(validBidRequests, adUnitCode); + const bidderRequest = generateBidderRequest(validBidRequests, adUnitCode, ortb2); return { bidRequest, validBidRequests, bidderRequest } }; @@ -331,6 +334,19 @@ describe('YahooSSP Bid Adapter:', () => { }); describe('Schain module support:', () => { + it('should not include schain data when schain array is empty', function () { + const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const globalSchain = { + ver: '1.0', + complete: 1, + nodes: [] + }; + bidRequest.schain = globalSchain; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const schain = data.source.ext.schain; + expect(schain).to.be.undefined; + }); + it('should send Global or Bidder specific schain', function () { const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); const globalSchain = { @@ -355,10 +371,9 @@ describe('YahooSSP Bid Adapter:', () => { // Should not allow invalid "site" data types const INVALID_ORTB2_TYPES = [ null, [], 123, 'unsupportedKeyName', true, false, undefined ]; INVALID_ORTB2_TYPES.forEach(param => { - const ortb2 = { site: param } - config.setConfig({ortb2}); it(`should not allow invalid site types to be added to bid-request: ${JSON.stringify(param)}`, () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const ortb2 = { site: param } + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site[param]).to.be.undefined; }); @@ -375,8 +390,7 @@ describe('YahooSSP Bid Adapter:', () => { [param]: 'something' } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site[param]).to.exist; expect(data.site[param]).to.be.a('string'); @@ -391,8 +405,7 @@ describe('YahooSSP Bid Adapter:', () => { [param]: ['something'] } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site[param]).to.exist; expect(data.site[param]).to.be.a('array'); @@ -408,8 +421,7 @@ describe('YahooSSP Bid Adapter:', () => { content: param } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site.content).to.be.undefined; }); @@ -426,8 +438,7 @@ describe('YahooSSP Bid Adapter:', () => { } } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site.content).to.be.a('object'); }); @@ -443,8 +454,7 @@ describe('YahooSSP Bid Adapter:', () => { } } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site.content[param]).to.exist; expect(data.site.content[param]).to.be.a('string'); @@ -462,8 +472,7 @@ describe('YahooSSP Bid Adapter:', () => { } } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site.content[param]).to.be.a('number'); expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); @@ -480,8 +489,7 @@ describe('YahooSSP Bid Adapter:', () => { } } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site.content[param]).to.be.a('array'); expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); @@ -498,8 +506,7 @@ describe('YahooSSP Bid Adapter:', () => { } } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.site.content[param]).to.be.a('object'); expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); @@ -513,10 +520,9 @@ describe('YahooSSP Bid Adapter:', () => { // Should not allow invalid "user" data types const INVALID_ORTB2_TYPES = [ null, [], 'unsupportedKeyName', true, false, undefined ]; INVALID_ORTB2_TYPES.forEach(param => { - const ortb2 = { user: param } - config.setConfig({ortb2}); it(`should not allow invalid site types to be added to bid-request: ${JSON.stringify(param)}`, () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const ortb2 = { user: param } + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.user[param]).to.be.undefined; }); @@ -531,8 +537,7 @@ describe('YahooSSP Bid Adapter:', () => { [param]: 'something' } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.user[param]).to.exist; expect(data.user[param]).to.be.a('string'); @@ -548,8 +553,7 @@ describe('YahooSSP Bid Adapter:', () => { [param]: 1982 } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.user[param]).to.exist; expect(data.user[param]).to.be.a('number'); @@ -565,8 +569,7 @@ describe('YahooSSP Bid Adapter:', () => { [param]: ['something'] } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.user[param]).to.exist; expect(data.user[param]).to.be.a('array'); @@ -582,8 +585,7 @@ describe('YahooSSP Bid Adapter:', () => { [param]: {a: '123', b: '456'} } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.user[param]).to.be.a('object'); expect(data.user[param]).to.be.deep.include({[param]: {a: '123', b: '456'}}); @@ -605,8 +607,7 @@ describe('YahooSSP Bid Adapter:', () => { } } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.user.data[0][param]).to.exist; expect(data.user.data[0][param]).to.be.a('string'); @@ -625,8 +626,7 @@ describe('YahooSSP Bid Adapter:', () => { data: [{[param]: [{id: 1}]}] } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.user.data[0][param]).to.exist; expect(data.user.data[0][param]).to.be.a('array'); @@ -642,8 +642,7 @@ describe('YahooSSP Bid Adapter:', () => { data: [{[param]: {id: 'ext'}}] } }; - config.setConfig({ortb2}); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.user.data[0][param]).to.exist; expect(data.user.data[0][param]).to.be.a('object'); @@ -823,6 +822,7 @@ describe('YahooSSP Bid Adapter:', () => { describe('Request Headers validation:', () => { it('should return request objects with the relevant custom headers and content type declaration', () => { const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + bidderRequest.gdprConsent.gdprApplies = false; const options = spec.buildRequests(validBidRequests, bidderRequest).options; expect(options).to.deep.equal( { @@ -841,7 +841,7 @@ describe('YahooSSP Bid Adapter:', () => { const data = spec.buildRequests(validBidRequests, bidderRequest).data; expect(data.site).to.deep.equal({ id: bidderRequest.bids[0].params.dcn, - page: bidderRequest.refererInfo.referer + page: bidderRequest.refererInfo.page }); expect(data.device).to.deep.equal({ diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index 833f883fb7c..f4b15d6dbc4 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -56,8 +56,6 @@ describe('Yandex adapter', function () { }); describe('buildRequests', function () { - const refererUrl = 'https://yandex.ru/secure-ads'; - const gdprConsent = { gdprApplies: 1, consentString: 'concent-string', @@ -66,7 +64,7 @@ describe('Yandex adapter', function () { const bidderRequest = { refererInfo: { - referer: refererUrl + domain: 'yandex.ru' }, gdprConsent }; diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index e4d258ecdea..4c39fe5fe29 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -3,79 +3,80 @@ import { expect } from 'chai' import { spec } from 'modules/yieldlabBidAdapter.js' import { newBidder } from 'src/adapters/bidderFactory.js' -const REQUEST = { - 'bidder': 'yieldlab', - 'params': { - 'adslotId': '1111', - 'supplyId': '2222', - 'targeting': { - 'key1': 'value1', - 'key2': 'value2', - 'notDoubleEncoded': 'value3,value4' +const DEFAULT_REQUEST = () => ({ + bidder: 'yieldlab', + params: { + adslotId: '1111', + supplyId: '2222', + targeting: { + key1: 'value1', + key2: 'value2', + notDoubleEncoded: 'value3,value4' }, - 'customParams': { - 'extraParam': true, - 'foo': 'bar' + customParams: { + extraParam: true, + foo: 'bar' }, - 'extId': 'abc', - 'iabContent': { - 'id': 'foo_id', - 'episode': '99', - 'title': 'foo_title,bar_title', - 'series': 'foo_series', - 'season': 's1', - 'artist': 'foo bar', - 'genre': 'baz', - 'isrc': 'CC-XXX-YY-NNNNN', - 'url': 'http://foo_url.de', - 'cat': ['cat1', 'cat2,ppp', 'cat3|||//'], - 'context': '7', - 'keywords': ['k1,', 'k2..'], - 'live': '0' + extId: 'abc', + iabContent: { + id: 'foo_id', + episode: '99', + title: 'foo_title,bar_title', + series: 'foo_series', + season: 's1', + artist: 'foo bar', + genre: 'baz', + isrc: 'CC-XXX-YY-NNNNN', + url: 'http://foo_url.de', + cat: ['cat1', 'cat2,ppp', 'cat3|||//'], + context: '7', + keywords: ['k1,', 'k2..'], + live: '0' } }, - 'bidderRequestId': '143346cf0f1731', - 'auctionId': '2e41f65424c87c', - 'adUnitCode': 'adunit-code', - 'bidId': '2d925f27f5079f', - 'sizes': [728, 90], - 'userIdAsEids': [{ - 'source': 'netid.de', - 'uids': [{ - 'id': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - 'atype': 1 + bidderRequestId: '143346cf0f1731', + auctionId: '2e41f65424c87c', + adUnitCode: 'adunit-code', + bidId: '2d925f27f5079f', + sizes: [728, 90], + userIdAsEids: [{ + source: 'netid.de', + uids: [{ + id: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', + atype: 1 }] }], - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ + schain: { + ver: '1.0', + complete: 1, + nodes: [ { - 'asi': 'indirectseller.com', - 'sid': '1', - 'hp': 1 + asi: 'indirectseller.com', + sid: '1', + hp: 1 }, { - 'asi': 'indirectseller2.com', - 'name': 'indirectseller2 name with comma , and bang !', - 'sid': '2', - 'hp': 1 + asi: 'indirectseller2.com', + name: 'indirectseller2 name with comma , and bang !', + sid: '2', + hp: 1 } ] } -} +}) -const VIDEO_REQUEST = Object.assign({}, REQUEST, { - 'mediaTypes': { - 'video': { - 'context': 'instream' +const VIDEO_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' } } }) -const NATIVE_REQUEST = Object.assign({}, REQUEST, { - 'mediaTypes': { - 'native': { } +const NATIVE_REQUEST = () => Object.assign(DEFAULT_REQUEST(), { + mediaTypes: { + native: {} } }) @@ -91,34 +92,34 @@ const RESPONSE = { } const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { - 'adtype': 'NATIVE', - 'native': { - 'link': { - 'url': 'https://www.yieldlab.de' + adtype: 'NATIVE', + native: { + link: { + url: 'https://www.yieldlab.de' }, - 'assets': [ + assets: [ { - 'id': 1, - 'title': { - 'text': 'This is a great headline' + id: 1, + title: { + text: 'This is a great headline' } }, { - 'id': 2, - 'img': { - 'url': 'https://localhost:8080/yl-logo100x100.jpg', - 'w': 100, - 'h': 100 + id: 2, + img: { + url: 'https://localhost:8080/yl-logo100x100.jpg', + w: 100, + h: 100 } }, { - 'id': 3, - 'data': { - 'value': 'Native body value' + id: 3, + data: { + value: 'Native body value' } } ], - 'imptrackers': [ + imptrackers: [ 'http://localhost:8080/ve?d=ODE9ZSY2MTI1MjAzNjMzMzYxPXN0JjA0NWUwZDk0NTY5Yi05M2FiLWUwZTQtOWFjNy1hYWY0MzFiZj1kaXQmMj12', 'http://localhost:8080/md/1111/9efa4e76-2030-4f04-bb9f-322541f8d611?mdata=false&pvid=false&ids=x:1', 'http://localhost:8080/imp?s=13216&d=2171514&a=12548955&ts=1633363025216&tid=fb134faa-7ca9-4e0e-ba39-b96549d0e540&l=0' @@ -127,11 +128,11 @@ const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { }) const VIDEO_RESPONSE = Object.assign({}, RESPONSE, { - 'adtype': 'VIDEO' + adtype: 'VIDEO' }) const PVID_RESPONSE = Object.assign({}, VIDEO_RESPONSE, { - 'pvid': '43513f11-55a0-4a83-94e5-0ebc08f54a2c' + pvid: '43513f11-55a0-4a83-94e5-0ebc08f54a2c' }) const REQPARAMS = { @@ -148,134 +149,214 @@ const REQPARAMS_IAB_CONTENT = Object.assign({}, REQPARAMS, { iab_content: 'id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0' }) -describe('yieldlabBidAdapter', function () { - const adapter = newBidder(spec) - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function') +describe('yieldlabBidAdapter', () => { + describe('instantiation from spec', () => { + it('is working properly', () => { + const yieldlabBidAdapter = newBidder(spec) + expect(yieldlabBidAdapter.callBids).to.exist.and.to.be.a('function') }) }) - describe('isBidRequestValid', function () { - it('should return true when required params found', function () { + describe('isBidRequestValid', () => { + it('should return true when all required parameters are found', () => { const request = { - 'params': { - 'adslotId': '1111', - 'supplyId': '2222' + params: { + adslotId: '1111', + supplyId: '2222' } } expect(spec.isBidRequestValid(request)).to.equal(true) }) - it('should return false when required params are not passed', function () { + it('should return false when required parameters are missing', () => { expect(spec.isBidRequestValid({})).to.equal(false) }) }) - describe('buildRequests', function () { - const bidRequests = [REQUEST] - const request = spec.buildRequests(bidRequests) + describe('buildRequests', () => { + const bidRequests = [DEFAULT_REQUEST()] - it('sends bid request to ENDPOINT via GET', function () { - expect(request.method).to.equal('GET') - }) + describe('default functionality', () => { + let request - it('returns a list of valid requests', function () { - expect(request.validBidRequests).to.eql([REQUEST]) - }) + before(() => { + request = spec.buildRequests(bidRequests) + }) - it('passes single-encoded targeting to bid request', function () { - expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4') - }) + it('sends bid request to ENDPOINT via GET', () => { + expect(request.method).to.equal('GET') + }) - it('passes userids to bid request', function () { - expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg') - }) + it('returns a list of valid requests', () => { + expect(request.validBidRequests).to.eql(bidRequests) + }) - it('passes extra params to bid request', function () { - expect(request.url).to.include('extraParam=true&foo=bar') - }) + it('passes single-encoded targeting to bid request', () => { + expect(request.url).to.include('t=key1%3Dvalue1%26key2%3Dvalue2%26notDoubleEncoded%3Dvalue3%2Cvalue4') + }) - it('passes unencoded schain string to bid request', function () { - expect(request.url).to.include('schain=1.0,1!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') - }) + it('passes userids to bid request', () => { + expect(request.url).to.include('ids=netid.de%3AfH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg') + }) + + it('passes extra params to bid request', () => { + expect(request.url).to.include('extraParam=true&foo=bar') + }) + + it('passes unencoded schain string to bid request', () => { + expect(request.url).to.include('schain=1.0,1!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') + }) + + it('passes iab_content string to bid request', () => { + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') + }) - it('passes iab_content string to bid request', function () { - expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') + it('passes correct size to bid request', () => { + expect(request.url).to.include('728x90') + }) + + it('passes external id to bid request', () => { + expect(request.url).to.include('id=abc') + }) }) - const siteConfig = { - 'ortb2': { - 'site': { - 'content': { - 'id': 'id_from_config' + describe('iab_content handling', () => { + const siteConfig = { + ortb2: { + site: { + content: { + id: 'id_from_config' + } } } } - } - it('generates iab_content string from bidder params', function () { - config.setConfig(siteConfig); - const request = spec.buildRequests([REQUEST]) - expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') - config.resetConfig(); - }) + beforeEach(() => { + config.setConfig(siteConfig) + }) - it('generates iab_content string from first party data if not provided in bidder params', function () { - const requestWithoutIabContent = { - 'params': { - 'adslotId': '1111', - 'supplyId': '2222' - } - } - config.setConfig(siteConfig); - const request = spec.buildRequests([requestWithoutIabContent]) - expect(request.url).to.include('iab_content=id%3Aid_from_config') - config.resetConfig(); - }) + afterEach(() => { + config.resetConfig() + }) - const refererRequest = spec.buildRequests(bidRequests, { - refererInfo: { - canonicalUrl: undefined, - numIframes: 0, - reachedTop: true, - referer: 'https://www.yieldlab.de/test?with=querystring', - stack: ['https://www.yieldlab.de/test?with=querystring'] - } + it('generates iab_content string from bidder params', () => { + const request = spec.buildRequests(bidRequests) + expect(request.url).to.include('iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') + }) + + it('generates iab_content string from first party data if not provided in bidder params', () => { + const requestWithoutIabContent = DEFAULT_REQUEST() + delete requestWithoutIabContent.params.iabContent + + const request = spec.buildRequests([{...requestWithoutIabContent, ...siteConfig}]) + expect(request.url).to.include('iab_content=id%3Aid_from_config') + }) }) - it('passes unencoded schain string to bid request when complete == 0', function () { - REQUEST.schain.complete = 0; - const request = spec.buildRequests([REQUEST]) + it('passes unencoded schain string to bid request when complete == 0', () => { + const schainRequest = DEFAULT_REQUEST() + schainRequest.schain.complete = 0; // + const request = spec.buildRequests([schainRequest]) expect(request.url).to.include('schain=1.0,0!indirectseller.com,1,1,,,,!indirectseller2.com,2,1,,indirectseller2%20name%20with%20comma%20%2C%20and%20bang%20%21,,') }) - it('passes encoded referer to bid request', function () { + it('passes encoded referer to bid request', () => { + const refererRequest = spec.buildRequests(bidRequests, { + refererInfo: { + canonicalUrl: undefined, + numIframes: 0, + reachedTop: true, + page: 'https://www.yieldlab.de/test?with=querystring', + stack: ['https://www.yieldlab.de/test?with=querystring'] + } + }) + expect(refererRequest.url).to.include('pubref=https%3A%2F%2Fwww.yieldlab.de%2Ftest%3Fwith%3Dquerystring') }) - const gdprRequest = spec.buildRequests(bidRequests, { - gdprConsent: { - consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', - gdprApplies: true - } - }) + it('passes gdpr flag and consent if present', () => { + const gdprRequest = spec.buildRequests(bidRequests, { + gdprConsent: { + consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', + gdprApplies: true + } + }) - it('passes gdpr flag and consent if present', function () { expect(gdprRequest.url).to.include('consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA') expect(gdprRequest.url).to.include('gdpr=true') }) + + describe('sizes handling', () => { + it('passes correct size to bid request for mediaType banner', () => { + const bannerRequest = DEFAULT_REQUEST(); + bannerRequest.mediaTypes = { + banner: { + sizes: [[123, 456]] + } + } + + // when mediaTypes is present it has precedence over the sizes field (728, 90) + let request = spec.buildRequests([bannerRequest], REQPARAMS) + expect(request.url).to.include('sizes') + expect(request.url).to.include('123x456') + + bannerRequest.mediaTypes.banner.sizes = [123, 456] + request = spec.buildRequests([bannerRequest], REQPARAMS) + expect(request.url).to.include('123x456') + + bannerRequest.mediaTypes.banner.sizes = [[123, 456], [320, 240]] + request = spec.buildRequests([bannerRequest], REQPARAMS) + expect(request.url).to.include('123x456') + expect(request.url).to.include('320x240') + }) + + it('passes correct sizes to bid request when mediaType is not present', () => { + // information is taken from the top level sizes field + const sizesRequest = DEFAULT_REQUEST(); + + let request = spec.buildRequests([sizesRequest], REQPARAMS) + expect(request.url).to.include('sizes') + expect(request.url).to.include('728x90') + + sizesRequest.sizes = [[728, 90]] + request = spec.buildRequests([sizesRequest], REQPARAMS) + expect(request.url).to.include('728x90') + + sizesRequest.sizes = [[728, 90], [320, 240]] + request = spec.buildRequests([sizesRequest], REQPARAMS) + expect(request.url).to.include('728x90') + }) + + it('does not pass the sizes parameter for mediaType video', () => { + const videoRequest = VIDEO_REQUEST(); + + let request = spec.buildRequests([videoRequest], REQPARAMS) + expect(request.url).to.not.include('sizes') + }) + + it('does not pass the sizes parameter for mediaType native', () => { + const nativeRequest = NATIVE_REQUEST(); + + let request = spec.buildRequests([nativeRequest], REQPARAMS) + expect(request.url).to.not.include('sizes') + }) + }) }) - describe('interpretResponse', function () { - it('handles nobid responses', function () { + describe('interpretResponse', () => { + let bidRequest + + before(() => { + bidRequest = DEFAULT_REQUEST() + }) + + it('handles nobid responses', () => { expect(spec.interpretResponse({body: {}}, {validBidRequests: []}).length).to.equal(0) expect(spec.interpretResponse({body: []}, {validBidRequests: []}).length).to.equal(0) }) - it('should get correct bid response', function () { - const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [REQUEST], queryParams: REQPARAMS}) + it('should get correct bid response', () => { + const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [bidRequest], queryParams: REQPARAMS}) expect(result[0].requestId).to.equal('2d925f27f5079f') expect(result[0].cpm).to.equal(0.01) @@ -292,21 +373,21 @@ describe('yieldlabBidAdapter', function () { expect(result[0].ad).to.include('&id=abc') }) - it('should append gdpr parameters to adtag', function () { - const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [REQUEST], queryParams: REQPARAMS_GDPR}) + it('should append gdpr parameters to adtag', () => { + const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [bidRequest], queryParams: REQPARAMS_GDPR}) expect(result[0].ad).to.include('&gdpr=true') expect(result[0].ad).to.include('&consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA') }) - it('should append iab_content to adtag', function () { - const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [REQUEST], queryParams: REQPARAMS_IAB_CONTENT}) + it('should append iab_content to adtag', () => { + const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [bidRequest], queryParams: REQPARAMS_IAB_CONTENT}) expect(result[0].ad).to.include('&iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') }) - it('should get correct bid response when passing more than one size', function () { - const REQUEST2 = Object.assign({}, REQUEST, { - 'sizes': [ + it('should get correct bid response when passing more than one size', () => { + const REQUEST2 = Object.assign(DEFAULT_REQUEST(), { + sizes: [ [800, 250], [728, 90], [970, 90], @@ -329,8 +410,8 @@ describe('yieldlabBidAdapter', function () { expect(result[0].ad).to.include('&id=abc') }) - it('should add vastUrl when type is video', function () { - const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST], queryParams: REQPARAMS}) + it('should add vastUrl when type is video', () => { + const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST()], queryParams: REQPARAMS}) expect(result[0].requestId).to.equal('2d925f27f5079f') expect(result[0].cpm).to.equal(0.01) @@ -339,8 +420,8 @@ describe('yieldlabBidAdapter', function () { expect(result[0].vastUrl).to.include('&id=abc') }) - it('should add adUrl and native assets when type is Native', function () { - const result = spec.interpretResponse({body: [NATIVE_RESPONSE]}, {validBidRequests: [NATIVE_REQUEST], queryParams: REQPARAMS}) + it('should add adUrl and native assets when type is Native', () => { + const result = spec.interpretResponse({body: [NATIVE_RESPONSE]}, {validBidRequests: [NATIVE_REQUEST()], queryParams: REQPARAMS}) expect(result[0].requestId).to.equal('2d925f27f5079f') expect(result[0].cpm).to.equal(0.01) @@ -355,17 +436,17 @@ describe('yieldlabBidAdapter', function () { expect(result[0].native.impressionTrackers.length).to.equal(3) }) - it('should add adUrl and default native assets when type is Native', function () { + it('should add adUrl and default native assets when type is Native', () => { const NATIVE_RESPONSE_2 = Object.assign({}, NATIVE_RESPONSE, { - 'native': { - 'link': { - 'url': 'https://www.yieldlab.de' + native: { + link: { + url: 'https://www.yieldlab.de' }, - 'assets': [], - 'imptrackers': [] + assets: [], + imptrackers: [] } }) - const result = spec.interpretResponse({body: [NATIVE_RESPONSE_2]}, {validBidRequests: [NATIVE_REQUEST], queryParams: REQPARAMS}) + const result = spec.interpretResponse({body: [NATIVE_RESPONSE_2]}, {validBidRequests: [NATIVE_REQUEST()], queryParams: REQPARAMS}) expect(result[0].requestId).to.equal('2d925f27f5079f') expect(result[0].cpm).to.equal(0.01) @@ -378,19 +459,19 @@ describe('yieldlabBidAdapter', function () { expect(result[0].native.image.height).to.equal(0) }) - it('should append gdpr parameters to vastUrl', function () { - const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST], queryParams: REQPARAMS_GDPR}) + it('should append gdpr parameters to vastUrl', () => { + const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST()], queryParams: REQPARAMS_GDPR}) expect(result[0].vastUrl).to.include('&gdpr=true') expect(result[0].vastUrl).to.include('&consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA') }) - it('should add renderer if outstream context', function () { - const OUTSTREAM_REQUEST = Object.assign({}, REQUEST, { - 'mediaTypes': { - 'video': { - 'playerSize': [[640, 480]], - 'context': 'outstream' + it('should add renderer if outstream context', () => { + const OUTSTREAM_REQUEST = Object.assign(DEFAULT_REQUEST(), { + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'outstream' } } }) @@ -402,27 +483,27 @@ describe('yieldlabBidAdapter', function () { expect(result[0].height).to.equal(480) }) - it('should add pvid to adtag urls when present', function () { - const result = spec.interpretResponse({body: [PVID_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST], queryParams: REQPARAMS}) + it('should add pvid to adtag urls when present', () => { + const result = spec.interpretResponse({body: [PVID_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST()], queryParams: REQPARAMS}) expect(result[0].ad).to.include('&pvid=43513f11-55a0-4a83-94e5-0ebc08f54a2c') expect(result[0].vastUrl).to.include('&pvid=43513f11-55a0-4a83-94e5-0ebc08f54a2c') }) - it('should append iab_content to vastUrl', function () { - const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST], queryParams: REQPARAMS_IAB_CONTENT}) + it('should append iab_content to vastUrl', () => { + const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST()], queryParams: REQPARAMS_IAB_CONTENT}) expect(result[0].vastUrl).to.include('&iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0') }) }) - describe('getUserSyncs', function () { + describe('getUserSyncs', () => { const syncOptions = { iframeEnabled: true, pixelEnabled: false }; const expectedUrlSnippets = ['https://ad.yieldlab.net/d/6846326/766/2x2?', 'ts=', 'type=h']; - it('should return user sync as expected', function () { + it('should return user sync as expected', () => { const bidRequest = { gdprConsent: { consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', @@ -439,7 +520,7 @@ describe('yieldlabBidAdapter', function () { expect(sync[0].type).to.have.string('iframe'); }); - it('should return user sync even without gdprApplies in gdprConsent', function () { + it('should return user sync even without gdprApplies in gdprConsent', () => { const gdprConsent = { consentString: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA' } @@ -450,4 +531,41 @@ describe('yieldlabBidAdapter', function () { expect(sync[0].type).to.have.string('iframe'); }); }); + + describe('getBidFloor', function () { + let bidRequest, getFloor; + + it('should add valid bid floor', () => { + getFloor = () => { + return { + currency: 'EUR', + floor: 1.33 + }; + }; + bidRequest = Object.assign(DEFAULT_REQUEST(), {getFloor}) + const result = spec.buildRequests([bidRequest], REQPARAMS) + expect(result).to.have.nested.property('queryParams.floor', 1.33) + }); + + it('should not add empty bid floor', () => { + getFloor = () => { + return {}; + }; + bidRequest = Object.assign(DEFAULT_REQUEST(), {getFloor}) + const result = spec.buildRequests([bidRequest], REQPARAMS) + expect(result).not.to.have.nested.property('queryParams.floor') + }); + + it('should not add bid floor when currency is not matching', () => { + getFloor = (currency, mediaType, size) => { + return { + currency: 'USD', + floor: 1.33 + }; + }; + bidRequest = Object.assign(DEFAULT_REQUEST(), {getFloor}) + const result = spec.buildRequests([bidRequest], REQPARAMS) + expect(result).not.to.have.nested.property('queryParams.floor') + }); + }); }) diff --git a/test/spec/modules/yieldliftBidAdapter_spec.js b/test/spec/modules/yieldliftBidAdapter_spec.js index 86a7b83e2c6..c2379ed7778 100644 --- a/test/spec/modules/yieldliftBidAdapter_spec.js +++ b/test/spec/modules/yieldliftBidAdapter_spec.js @@ -45,7 +45,7 @@ const RESPONSE = { 'price': 0.18, 'adm': '', 'adid': '144762342', - 'adomain': [ + 'advertiserDomains': [ 'https://dummydomain.com' ], 'iurl': 'iurl', @@ -74,7 +74,7 @@ const RESPONSE = { 'price': 0.1, 'adm': '', 'adid': '144762342', - 'adomain': [ + 'advertiserDomains': [ 'https://dummydomain.com' ], 'iurl': 'iurl', @@ -191,6 +191,26 @@ describe('YieldLift', function () { expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); expect(payload.regs.ext).to.have.property('gdpr', 1); }); + + it('should properly forward eids parameters', function () { + const req = Object.assign({}, REQUEST); + req.bidRequest[0].userIdAsEids = [ + { + source: 'dummy.com', + uids: [ + { + id: 'd6d0a86c-20c6-4410-a47b-5cba383a698a', + atype: 1 + } + ] + }]; + let request = spec.buildRequests(req.bidRequest, req); + + const payload = JSON.parse(request.data); + expect(payload.user.ext.eids[0].source).to.equal('dummy.com'); + expect(payload.user.ext.eids[0].uids[0].id).to.equal('d6d0a86c-20c6-4410-a47b-5cba383a698a'); + expect(payload.user.ext.eids[0].uids[0].atype).to.equal(1); + }); }); describe('interpretResponse', function () { @@ -208,7 +228,7 @@ describe('YieldLift', function () { expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); - expect(bids[index].meta).to.have.property('adomain', RESPONSE.body.seatbid[0].bid[index].adomain); + expect(bids[index].meta).to.have.property('advertiserDomains', RESPONSE.body.seatbid[0].bid[index].advertiserDomains); expect(bids[index]).to.have.property('ttl', 30); expect(bids[index]).to.have.property('netRevenue', true); } diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index f72705a79ac..32f0ead11f1 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -2,9 +2,13 @@ import { expect } from 'chai'; import { spec } from 'modules/yieldmoBidAdapter.js'; import * as utils from 'src/utils.js'; +/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ +// above is used for debugging purposes only + describe('YieldmoAdapter', function () { const BANNER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid'; const VIDEO_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo'; + const PB_COOKIE_ASSIST_SYNC_ENDPOINT = `https://ads.yieldmo.com/pbcas`; const mockBannerBid = (rootParams = {}, params = {}) => ({ bidder: 'yieldmo', @@ -37,6 +41,7 @@ describe('YieldmoAdapter', function () { bidder: 'yieldmo', adUnitCode: 'adunit-code-video', bidId: '321video123', + auctionId: '1d1a03073455', mediaTypes: { video: { playerSize: [640, 480], @@ -171,15 +176,15 @@ describe('YieldmoAdapter', function () { it('should place bid information into the p parameter of data', function () { let bidArray = [mockBannerBid()]; expect(buildAndGetPlacementInfo(bidArray)).to.equal( - '[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1}]' + '[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1,"auctionId":"1d1a030790a475"}]' ); // multiple placements bidArray.push(mockBannerBid( {adUnitCode: 'adunit-2', bidId: '123a', bidderRequestId: '321', auctionId: '222'}, {bidFloor: 0.2})); expect(buildAndGetPlacementInfo(bidArray)).to.equal( - '[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1},' + - '{"placement_id":"adunit-2","callback_id":"123a","sizes":[[300,250],[300,600]],"bidFloor":0.2}]' + '[{"placement_id":"adunit-code","callback_id":"30b31c1838de1e","sizes":[[300,250],[300,600]],"bidFloor":0.1,"auctionId":"1d1a030790a475"},' + + '{"placement_id":"adunit-2","callback_id":"123a","sizes":[[300,250],[300,600]],"bidFloor":0.2,"auctionId":"222"}]' ); }); @@ -217,6 +222,24 @@ describe('YieldmoAdapter', function () { expect(buildAndGetData([pubcidBid]).pubcid).to.deep.equal(pubcid); }); + it('should add transaction id as parameter of request', function () { + const transactionId = '54a58774-7a41-494e-9aaf-fa7b79164f0c'; + const pubcidBid = mockBannerBid({ ortb2Imp: { + ext: { + tid: '54a58774-7a41-494e-9aaf-fa7b79164f0c', + } + }}); + const bidRequest = buildAndGetData([pubcidBid]); + expect(bidRequest.p).to.contain(transactionId); + }); + + it('should add auction id as parameter of request', function () { + const auctionId = '1d1a030790a475'; + const pubcidBid = mockBannerBid({}); + const bidRequest = buildAndGetData([pubcidBid]); + expect(bidRequest.p).to.contain(auctionId); + }); + it('should add unified id as parameter of request', function () { const unifiedIdBid = mockBannerBid({crumbs: undefined}); expect(buildAndGetData([unifiedIdBid]).tdid).to.deep.equal(mockBannerBid().userId.tdid); @@ -470,6 +493,24 @@ describe('YieldmoAdapter', function () { expect(requests[0].data.ats_envelope).to.equal(envelope); }); + it('should add transaction id to video bid request', function() { + const transactionId = '54a58774-7a41-494e-8cbc-fa7b79164f0c'; + const requestData = { + ortb2Imp: { + ext: { + tid: '54a58774-7a41-494e-8cbc-fa7b79164f0c', + } + } + }; + expect(buildAndGetData([mockVideoBid({...requestData})]).imp[0].ext.tid).to.equal(transactionId); + }); + + it('should add auction id to video bid request', function() { + const auctionId = '1d1a03073455'; + + expect(buildAndGetData([mockVideoBid({})]).auctionId).to.deep.equal(auctionId); + }); + it('should add schain if it is in the bidRequest', () => { const schain = { ver: '1.0', @@ -606,8 +647,20 @@ describe('YieldmoAdapter', function () { }); describe('getUserSync', function () { - it('should return a tracker with type and url as parameters', function () { - expect(spec.getUserSyncs()).to.deep.equal([]); + const gdprFlag = `&gdpr=0`; + const usPrivacy = `us_privacy=`; + const gdprString = `&gdpr_consent=`; + const pbCookieAssistSyncUrl = `${PB_COOKIE_ASSIST_SYNC_ENDPOINT}?${usPrivacy}${gdprFlag}${gdprString}`; + it('should use type iframe when iframeEnabled', function() { + const syncs = spec.getUserSyncs({iframeEnabled: true}); + expect(syncs).to.deep.equal([{type: 'iframe', url: pbCookieAssistSyncUrl + '&type=iframe'}]) + }); + it('should use type image when pixelEnabled', function() { + const syncs = spec.getUserSyncs({pixelEnabled: true}); + expect(syncs).to.deep.equal([{type: 'image', url: pbCookieAssistSyncUrl + '&type=image'}]) + }); + it('should register no syncs', function () { + expect(spec.getUserSyncs({})).to.deep.equal([]); }); }); }); diff --git a/test/spec/modules/yieldoneAnalyticsAdapter_spec.js b/test/spec/modules/yieldoneAnalyticsAdapter_spec.js index 81a6365bba2..f55577913a5 100644 --- a/test/spec/modules/yieldoneAnalyticsAdapter_spec.js +++ b/test/spec/modules/yieldoneAnalyticsAdapter_spec.js @@ -46,7 +46,7 @@ describe('Yieldone Prebid Analytic', function () { { bidderCode: 'biddertest_1', auctionId: auctionId, - refererInfo: {referer: testReferrer}, + refererInfo: {page: testReferrer}, bids: [ { adUnitCode: '0000', @@ -71,7 +71,7 @@ describe('Yieldone Prebid Analytic', function () { { bidderCode: 'biddertest_2', auctionId: auctionId, - refererInfo: {referer: testReferrer}, + refererInfo: {page: testReferrer}, bids: [ { adUnitCode: '0000', @@ -87,7 +87,7 @@ describe('Yieldone Prebid Analytic', function () { { bidderCode: 'biddertest_3', auctionId: auctionId, - refererInfo: {referer: testReferrer}, + refererInfo: {page: testReferrer}, bids: [ { adUnitCode: '0000', @@ -269,7 +269,11 @@ describe('Yieldone Prebid Analytic', function () { setTimeout(function() { events.emit(constants.EVENTS.BID_WON, winner); - sinon.assert.callCount(sendStatStub, 2); + sinon.assert.callCount(sendStatStub, 2) + const billableEventIndex = yieldoneAnalytics.eventsStorage[auctionId].events.findIndex(event => event.eventType === constants.EVENTS.BILLABLE_EVENT); + if (billableEventIndex > -1) { + yieldoneAnalytics.eventsStorage[auctionId].events.splice(billableEventIndex, 1); + } expect(yieldoneAnalytics.eventsStorage[auctionId]).to.deep.equal(wonExpectedResult); delete yieldoneAnalytics.eventsStorage[auctionId]; diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index d452d78e147..e41764b2876 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -399,6 +399,76 @@ describe('yieldoneBidAdapter', function() { expect(request[0].data.imuid).to.equal('imuid_sample'); }); }); + + describe('DAC ID', function () { + it('dont send DAC ID if undefined', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + }, + { + params: {placementId: '1'}, + userId: {}, + }, + { + params: {placementId: '2'}, + userId: undefined, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data).to.not.have.property('dac_id'); + expect(request[1].data).to.not.have.property('dac_id'); + expect(request[2].data).to.not.have.property('dac_id'); + expect(request[0].data).to.not.have.property('fuuid'); + expect(request[1].data).to.not.have.property('fuuid'); + expect(request[2].data).to.not.have.property('fuuid'); + }); + + it('should send DAC ID if available', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + userId: {dacId: {id: 'dacId_sample'}}, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.dac_id).to.equal('dacId_sample'); + expect(request[0].data.fuuid).to.equal('dacId_sample'); + }); + }); + + describe('ID5', function () { + it('dont send ID5 if undefined', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + }, + { + params: {placementId: '1'}, + userId: {}, + }, + { + params: {placementId: '2'}, + userId: undefined, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data).to.not.have.property('id5Id'); + expect(request[1].data).to.not.have.property('id5Id'); + expect(request[2].data).to.not.have.property('id5Id'); + }); + + it('should send ID5 if available', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + userId: {id5id: {uid: 'id5id_sample'}}, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].data.id5Id).to.equal('id5id_sample'); + }); + }); }); describe('interpretResponse', function () { @@ -413,7 +483,9 @@ describe('yieldoneBidAdapter', function() { 'cb': 12892917383, 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', 'uid': '23beaa6af6cdde', - 't': 'i' + 't': 'i', + 'language': 'ja', + 'screen_size': '1440x900' } } ]; @@ -486,7 +558,9 @@ describe('yieldoneBidAdapter', function() { 'cb': 12892917383, 'r': 'http%3A%2F%2Flocalhost%3A9876%2F%3Fid%3D74552836', 'uid': '23beaa6af6cdde', - 't': 'i' + 't': 'i', + 'language': 'ja', + 'screen_size': '1440x900' } } ]; diff --git a/test/spec/modules/zetaBidAdapter_spec.js b/test/spec/modules/zetaBidAdapter_spec.js index 25350725dee..529fb8e8d31 100644 --- a/test/spec/modules/zetaBidAdapter_spec.js +++ b/test/spec/modules/zetaBidAdapter_spec.js @@ -10,7 +10,7 @@ describe('Zeta Bid Adapter', function() { } }, refererInfo: { - referer: 'testprebid.com' + page: 'testprebid.com' }, params: { placement: 12345, diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index d439da8e711..3bd17697f2d 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -44,6 +44,48 @@ describe('Zeta Ssp Bid Adapter', function () { test: 1 }; + const multiImpRequest = [ + { + bidId: 12345, + auctionId: 67890, + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + refererInfo: { + page: 'http://www.zetaglobal.com/page?param=value', + domain: 'www.zetaglobal.com', + }, + gdprConsent: { + gdprApplies: 1, + consentString: 'consentString' + }, + uspConsent: 'someCCPAString', + params: params, + userIdAsEids: eids + }, { + bidId: 54321, + auctionId: 67890, + mediaTypes: { + banner: { + sizes: [[600, 400]], + } + }, + refererInfo: { + page: 'http://www.zetaglobal.com/page?param=value', + domain: 'www.zetaglobal.com', + }, + gdprConsent: { + gdprApplies: 1, + consentString: 'consentString' + }, + uspConsent: 'someCCPAString', + params: params, + userIdAsEids: eids + } + ]; + const bannerRequest = [{ bidId: 12345, auctionId: 67890, @@ -53,7 +95,8 @@ describe('Zeta Ssp Bid Adapter', function () { } }, refererInfo: { - referer: 'http://www.zetaglobal.com/page?param=value' + page: 'http://www.zetaglobal.com/page?param=value', + domain: 'www.zetaglobal.com', }, gdprConsent: { gdprApplies: 1, @@ -108,7 +151,7 @@ describe('Zeta Ssp Bid Adapter', function () { const request = spec.buildRequests(bannerRequest, bannerRequest[0]); const payload = JSON.parse(request.data); expect(payload.site.page).to.eql('http://www.zetaglobal.com/page?param=value'); - expect(payload.site.domain).to.eql(window.location.origin); // config.js -> DEFAULT_PUBLISHER_DOMAIN + expect(payload.site.domain).to.eql('zetaglobal.com'); }); it('Test the request processing function', function () { @@ -270,7 +313,7 @@ describe('Zeta Ssp Bid Adapter', function () { it('Test required params in banner request', function () { const request = spec.buildRequests(bannerRequest, bannerRequest[0]); const payload = JSON.parse(request.data); - expect(request.url).to.eql('https://ssp.disqus.com/bid?shortname=test_shortname'); + expect(request.url).to.eql('https://ssp.disqus.com/bid/prebid?shortname=test_shortname'); expect(payload.ext.sid).to.eql('publisherId'); expect(payload.ext.tags.someTag).to.eql(444); expect(payload.ext.tags.shortname).to.be.undefined; @@ -279,9 +322,26 @@ describe('Zeta Ssp Bid Adapter', function () { it('Test required params in video request', function () { const request = spec.buildRequests(videoRequest, videoRequest[0]); const payload = JSON.parse(request.data); - expect(request.url).to.eql('https://ssp.disqus.com/bid?shortname=test_shortname'); + expect(request.url).to.eql('https://ssp.disqus.com/bid/prebid?shortname=test_shortname'); expect(payload.ext.sid).to.eql('publisherId'); expect(payload.ext.tags.someTag).to.eql(444); expect(payload.ext.tags.shortname).to.be.undefined; }); + + it('Test multi imp', function () { + const request = spec.buildRequests(multiImpRequest, multiImpRequest[0]); + const payload = JSON.parse(request.data); + expect(request.url).to.eql('https://ssp.disqus.com/bid/prebid?shortname=test_shortname'); + + expect(payload.imp.length).to.eql(2); + + expect(payload.imp[0].id).to.eql(12345); + expect(payload.imp[1].id).to.eql(54321); + + expect(payload.imp[0].banner.w).to.eql(300); + expect(payload.imp[0].banner.h).to.eql(250); + + expect(payload.imp[1].banner.w).to.eql(600); + expect(payload.imp[1].banner.h).to.eql(400); + }); }); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 66e11b9a472..72eee1be85d 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -5,10 +5,14 @@ import { nativeBidIsValid, getAssetMessage, getAllAssetsMessage, - decorateAdUnitsWithNativeParams + decorateAdUnitsWithNativeParams, + isOpenRTBBidRequestValid, + isNativeOpenRTBBidValid, + toOrtbNativeRequest, toOrtbNativeResponse, legacyPropertiesToOrtbNative, fireImpressionTrackers, fireClickTrackers, } from 'src/native.js'; import CONSTANTS from 'src/constants.json'; -import {stubAuctionIndex} from '../helpers/indexStub.js'; +import { stubAuctionIndex } from '../helpers/indexStub.js'; +import { convertOrtbRequestToProprietaryNative, fromOrtbNativeRequest } from '../../src/native.js'; const utils = require('src/utils'); const bid = { @@ -21,25 +25,125 @@ const bid = { image: { url: 'http://cdn.example.com/p/creative-image/image.png', height: 83, - width: 127 + width: 127, }, icon: { url: 'http://cdn.example.com/p/creative-image/icon.jpg', height: 742, - width: 989 + width: 989, }, sponsoredBy: 'AppNexus', clickUrl: 'https://www.link.example', clickTrackers: ['https://tracker.example'], impressionTrackers: ['https://impression.example'], - javascriptTrackers: '', + javascriptTrackers: '', ext: { foo: 'foo-value', - baz: 'baz-value' + baz: 'baz-value', + }, + }, +}; + +const ortbBid = { + adId: '123', + transactionId: 'au', + native: { + ortb: { + assets: [ + { + id: 0, + title: { + text: 'Native Creative' + } + }, + { + id: 1, + data: { + value: 'Cool description great stuff' + } + }, + { + id: 2, + data: { + value: 'Do it' + } + }, + { + id: 3, + img: { + url: 'http://cdn.example.com/p/creative-image/image.png', + h: 83, + w: 127 + } + }, + { + id: 4, + img: { + url: 'http://cdn.example.com/p/creative-image/icon.jpg', + h: 742, + w: 989 + } + }, + { + id: 5, + data: { + value: 'AppNexus', + type: 1 + } + } + ], + link: { + url: 'https://www.link.example' + }, + privacy: 'https://privacy-link.example', + ver: '1.2' } - } + }, }; +const ortbRequest = { + assets: [ + { + id: 0, + required: 0, + title: { + len: 140 + } + }, { + id: 1, + required: 0, + data: { + type: 2 + } + }, { + id: 2, + required: 0, + data: { + type: 12 + } + }, { + id: 3, + required: 0, + img: { + type: 3 + } + }, { + id: 4, + required: 0, + img: { + type: 1 + } + }, { + id: 5, + required: 0, + data: { + type: 1 + } + } + ], + ver: '1.2' +} + const bidWithUndefinedFields = { transactionId: 'au', native: { @@ -50,12 +154,12 @@ const bidWithUndefinedFields = { clickUrl: 'https://www.link.example', clickTrackers: ['https://tracker.example'], impressionTrackers: ['https://impression.example'], - javascriptTrackers: '', + javascriptTrackers: '', ext: { foo: 'foo-value', - baz: undefined - } - } + baz: undefined, + }, + }, }; describe('native.js', function () { @@ -80,7 +184,9 @@ describe('native.js', function () { const targeting = getNativeTargeting(bid); expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal(bid.native.body); - expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal(bid.native.clickUrl); + expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal( + bid.native.clickUrl + ); expect(targeting.hb_native_foo).to.equal(bid.native.foo); }); @@ -92,19 +198,23 @@ describe('native.js', function () { clickUrl: { sendId: true }, ext: { foo: { - sendId: false + sendId: false, }, baz: { - sendId: true - } - } - } + sendId: true, + }, + }, + }, }; const targeting = getNativeTargeting(bid, deps(adUnit)); expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); - expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal('hb_native_body:123'); - expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal('hb_native_linkurl:123'); + expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal( + 'hb_native_body:123' + ); + expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal( + 'hb_native_linkurl:123' + ); expect(targeting.hb_native_foo).to.equal(bid.native.ext.foo); expect(targeting.hb_native_baz).to.equal('hb_native_baz:123'); }); @@ -117,13 +227,13 @@ describe('native.js', function () { clickUrl: { sendId: true }, ext: { foo: { - required: false + required: false, }, baz: { - required: false - } - } - } + required: false, + }, + }, + }, }; const targeting = getNativeTargeting(bidWithUndefinedFields, deps(adUnit)); @@ -132,7 +242,7 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.title, CONSTANTS.NATIVE_KEYS.sponsoredBy, CONSTANTS.NATIVE_KEYS.clickUrl, - 'hb_native_foo' + 'hb_native_foo', ]); }); @@ -142,22 +252,19 @@ describe('native.js', function () { nativeParams: { image: { required: true, - sizes: [150, 50] + sizes: [150, 50], }, title: { required: true, len: 80, - sendTargetingKeys: true + sendTargetingKeys: true, }, sendTargetingKeys: false, - } - + }, }; const targeting = getNativeTargeting(bid, deps(adUnit)); - expect(Object.keys(targeting)).to.deep.equal([ - CONSTANTS.NATIVE_KEYS.title - ]); + expect(Object.keys(targeting)).to.deep.equal([CONSTANTS.NATIVE_KEYS.title]); }); it('should only include targeting if sendTargetingKeys not set to false', function () { @@ -166,38 +273,37 @@ describe('native.js', function () { nativeParams: { image: { required: true, - sizes: [150, 50] + sizes: [150, 50], }, title: { required: true, - len: 80 + len: 80, }, body: { - required: true + required: true, }, clickUrl: { - required: true + required: true, }, icon: { required: false, - sendTargetingKeys: false + sendTargetingKeys: false, }, cta: { required: false, - sendTargetingKeys: false + sendTargetingKeys: false, }, sponsoredBy: { required: false, - sendTargetingKeys: false + sendTargetingKeys: false, }, ext: { foo: { required: false, - sendTargetingKeys: true - } - } - } - + sendTargetingKeys: true, + }, + }, + }, }; const targeting = getNativeTargeting(bid, deps(adUnit)); @@ -206,7 +312,7 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.body, CONSTANTS.NATIVE_KEYS.image, CONSTANTS.NATIVE_KEYS.clickUrl, - 'hb_native_foo' + 'hb_native_foo', ]); }); @@ -216,17 +322,16 @@ describe('native.js', function () { nativeParams: { image: { required: true, - sizes: [150, 50] + sizes: [150, 50], }, title: { required: true, len: 80, }, rendererUrl: { - url: 'https://www.renderer.com/' - } - } - + url: 'https://www.renderer.com/', + }, + }, }; const targeting = getNativeTargeting(bid, deps(adUnit)); @@ -238,7 +343,7 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.icon, CONSTANTS.NATIVE_KEYS.sponsoredBy, CONSTANTS.NATIVE_KEYS.clickUrl, - CONSTANTS.NATIVE_KEYS.rendererUrl + CONSTANTS.NATIVE_KEYS.rendererUrl, ]); expect(bid.native.rendererUrl).to.deep.equal('https://www.renderer.com/'); @@ -251,15 +356,14 @@ describe('native.js', function () { nativeParams: { image: { required: true, - sizes: [150, 50] + sizes: [150, 50], }, title: { required: true, len: 80, }, - adTemplate: '

##hb_native_body##<\/p><\/div>' - } - + adTemplate: '

##hb_native_body##

', + }, }; const targeting = getNativeTargeting(bid, deps(adUnit)); @@ -270,10 +374,12 @@ describe('native.js', function () { CONSTANTS.NATIVE_KEYS.image, CONSTANTS.NATIVE_KEYS.icon, CONSTANTS.NATIVE_KEYS.sponsoredBy, - CONSTANTS.NATIVE_KEYS.clickUrl + CONSTANTS.NATIVE_KEYS.clickUrl, ]); - expect(bid.native.adTemplate).to.deep.equal('

##hb_native_body##<\/p><\/div>'); + expect(bid.native.adTemplate).to.deep.equal( + '

##hb_native_body##

' + ); delete bid.native.adTemplate; }); @@ -281,7 +387,10 @@ describe('native.js', function () { fireNativeTrackers({}, bid); sinon.assert.calledOnce(triggerPixelStub); sinon.assert.calledWith(triggerPixelStub, bid.native.impressionTrackers[0]); - sinon.assert.calledWith(insertHtmlIntoIframeStub, bid.native.javascriptTrackers); + sinon.assert.calledWith( + insertHtmlIntoIframeStub, + bid.native.javascriptTrackers + ); }); it('fires click trackers', function () { @@ -291,7 +400,7 @@ describe('native.js', function () { sinon.assert.calledWith(triggerPixelStub, bid.native.clickTrackers[0]); }); - it('creates native asset message', function() { + it('creates native asset message', function () { const messageRequest = { message: 'Prebid Native', action: 'assetRequest', @@ -304,92 +413,270 @@ describe('native.js', function () { expect(message.assets.length).to.equal(3); expect(message.assets).to.deep.include({ key: 'body', - value: bid.native.body + value: bid.native.body, }); expect(message.assets).to.deep.include({ key: 'image', - value: bid.native.image.url + value: bid.native.image.url, }); expect(message.assets).to.deep.include({ key: 'clickUrl', - value: bid.native.clickUrl + value: bid.native.clickUrl, }); }); - it('creates native all asset message', function() { + it('creates native all asset message', function () { const messageRequest = { message: 'Prebid Native', action: 'allAssetRequest', adId: '123', }; - const message = getAllAssetsMessage(messageRequest, bid); + const message = getAllAssetsMessage(messageRequest, bid, {getNativeReq: () => null}); expect(message.assets.length).to.equal(9); expect(message.assets).to.deep.include({ key: 'body', - value: bid.native.body + value: bid.native.body, }); expect(message.assets).to.deep.include({ key: 'image', - value: bid.native.image.url + value: bid.native.image.url, }); expect(message.assets).to.deep.include({ key: 'clickUrl', - value: bid.native.clickUrl + value: bid.native.clickUrl, }); expect(message.assets).to.deep.include({ key: 'title', - value: bid.native.title + value: bid.native.title, }); expect(message.assets).to.deep.include({ key: 'icon', - value: bid.native.icon.url + value: bid.native.icon.url, }); expect(message.assets).to.deep.include({ key: 'cta', - value: bid.native.cta + value: bid.native.cta, }); expect(message.assets).to.deep.include({ key: 'sponsoredBy', - value: bid.native.sponsoredBy + value: bid.native.sponsoredBy, }); expect(message.assets).to.deep.include({ key: 'foo', - value: bid.native.ext.foo + value: bid.native.ext.foo, }); expect(message.assets).to.deep.include({ key: 'baz', - value: bid.native.ext.baz + value: bid.native.ext.baz, }); }); - it('creates native all asset message with only defined fields', function() { + it('creates native all asset message with only defined fields', function () { const messageRequest = { message: 'Prebid Native', action: 'allAssetRequest', adId: '123', }; - const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields); + const message = getAllAssetsMessage(messageRequest, bidWithUndefinedFields, {getNativeReq: () => null}); expect(message.assets.length).to.equal(4); expect(message.assets).to.deep.include({ key: 'clickUrl', - value: bid.native.clickUrl + value: bid.native.clickUrl, }); expect(message.assets).to.deep.include({ key: 'title', - value: bid.native.title + value: bid.native.title, }); expect(message.assets).to.deep.include({ key: 'sponsoredBy', - value: bid.native.sponsoredBy + value: bid.native.sponsoredBy, }); expect(message.assets).to.deep.include({ key: 'foo', - value: bid.native.ext.foo + value: bid.native.ext.foo, + }); + }); + + it('creates native all asset message with OpenRTB format', function () { + const messageRequest = { + message: 'Prebid Native', + action: 'allAssetRequest', + adId: '123', + }; + + const message = getAllAssetsMessage(messageRequest, ortbBid, {getNativeReq: () => ortbRequest}); + + expect(message.assets.length).to.equal(8); + expect(message.assets).to.deep.include({ + key: 'body', + value: bid.native.body, + }); + expect(message.assets).to.deep.include({ + key: 'image', + value: bid.native.image.url, + }); + expect(message.assets).to.deep.include({ + key: 'clickUrl', + value: bid.native.clickUrl, + }); + expect(message.assets).to.deep.include({ + key: 'title', + value: bid.native.title, + }); + expect(message.assets).to.deep.include({ + key: 'icon', + value: bid.native.icon.url, + }); + expect(message.assets).to.deep.include({ + key: 'cta', + value: bid.native.cta, + }); + expect(message.assets).to.deep.include({ + key: 'sponsoredBy', + value: bid.native.sponsoredBy, }); + expect(message.assets).to.deep.include({ + key: 'privacyLink', + value: ortbBid.native.ortb.privacy, + }); + }); + + const SAMPLE_ORTB_REQUEST = toOrtbNativeRequest({ + title: 'vtitle', + body: 'vbody' + }); + const SAMPLE_ORTB_RESPONSE = { + native: { + ortb: { + link: { + url: 'url' + }, + assets: [ + { + id: 0, + title: { + text: 'vtitle' + } + }, + { + id: 1, + data: { + value: 'vbody' + } + } + ] + } + } + } + describe('getAllAssetsMessage', () => { + it('returns assets in legacy format for ortb responses', () => { + const actual = getAllAssetsMessage({}, SAMPLE_ORTB_RESPONSE, {getNativeReq: () => SAMPLE_ORTB_REQUEST}); + expect(actual.assets).to.eql([ + { + key: 'clickUrl', + value: 'url' + }, + { + key: 'title', + value: 'vtitle' + }, + { + key: 'body', + value: 'vbody' + }, + ]) + }); + }); + describe('getAssetsMessage', () => { + Object.entries({ + 'hb_native_title': {key: 'title', value: 'vtitle'}, + 'hb_native_body': {key: 'body', value: 'vbody'} + }).forEach(([tkey, assetVal]) => { + it(`returns ${tkey} asset in legacy format for ortb responses`, () => { + const actual = getAssetMessage({ + assets: [tkey] + }, SAMPLE_ORTB_RESPONSE, {getNativeReq: () => SAMPLE_ORTB_REQUEST}) + expect(actual.assets).to.eql([assetVal]) + }) + }) + }) +}); + +describe('validate native openRTB', function () { + it('should validate openRTB request', function () { + let openRTBNativeRequest = { assets: [] }; + // assets array can't be empty + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); + openRTBNativeRequest.assets.push({ + id: 1.5, + required: 1, + title: {}, + }); + + // asset.id must be integer + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); + openRTBNativeRequest.assets[0].id = 1; + // title must have 'len' property + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); + openRTBNativeRequest.assets[0].title.len = 140; + // openRTB request is valid + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(true); + + openRTBNativeRequest.assets.push({ + id: 2, + required: 1, + video: { + mimes: [], + protocols: [], + minduration: 50, + }, + }); + // video asset should have all required properties + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(false); + openRTBNativeRequest.assets[1].video.maxduration = 60; + expect(isOpenRTBBidRequestValid(openRTBNativeRequest)).to.eq(true); + }); + + it('should validate openRTB native bid', function () { + const openRTBRequest = { + assets: [ + { + id: 1, + required: 1, + }, + { + id: 2, + required: 0, + }, + { + id: 3, + required: 1, + }, + ], + }; + let openRTBBid = { + assets: [ + { + id: 1, + }, + { + id: 2, + }, + ], + }; + + // link is missing + expect(isNativeOpenRTBBidValid(openRTBBid, openRTBRequest)).to.eq(false); + openRTBBid.link = { url: 'www.foo.bar' }; + // required id == 3 is missing + expect(isNativeOpenRTBBidValid(openRTBBid, openRTBRequest)).to.eq(false); + + openRTBBid.assets[1].id = 3; + expect(isNativeOpenRTBBidValid(openRTBBid, openRTBRequest)).to.eq(true); }); }); @@ -407,15 +694,15 @@ describe('validate native', function () { image: { required: true, sizes: [150, 50], - aspect_ratios: [150, 50] + aspect_ratios: [150, 50], }, icon: { required: true, - sizes: [50, 50] + sizes: [50, 50], }, - } - } - } + }, + }, + }; let validBid = { adId: 'abc123', @@ -424,23 +711,24 @@ describe('validate native', function () { adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { - body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + body: + 'This is a Prebid Native Creative. There are many like it, but this one is mine.', clickTrackers: ['http://my.click.tracker/url'], icon: { url: 'http://my.image.file/ad_image.jpg', height: 75, - width: 75 + width: 75, }, image: { url: 'http://my.icon.file/ad_icon.jpg', height: 2250, - width: 3000 + width: 3000, }, clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', impressionTrackers: ['http://my.imp.tracker/url'], - javascriptTrackers: '', - title: 'This is an example Prebid Native creative' - } + javascriptTrackers: '', + title: 'This is an example Prebid Native creative', + }, }; let noIconDimBid = { @@ -450,19 +738,20 @@ describe('validate native', function () { adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { - body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + body: + 'This is a Prebid Native Creative. There are many like it, but this one is mine.', clickTrackers: ['http://my.click.tracker/url'], icon: 'http://my.image.file/ad_image.jpg', image: { url: 'http://my.icon.file/ad_icon.jpg', height: 2250, - width: 3000 + width: 3000, }, clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', impressionTrackers: ['http://my.imp.tracker/url'], - javascriptTrackers: '', - title: 'This is an example Prebid Native creative' - } + javascriptTrackers: '', + title: 'This is an example Prebid Native creative', + }, }; let noImgDimBid = { @@ -472,19 +761,20 @@ describe('validate native', function () { adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { - body: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + body: + 'This is a Prebid Native Creative. There are many like it, but this one is mine.', clickTrackers: ['http://my.click.tracker/url'], icon: { url: 'http://my.image.file/ad_image.jpg', height: 75, - width: 75 + width: 75, }, image: 'http://my.icon.file/ad_icon.jpg', clickUrl: 'http://prebid.org/dev-docs/show-native-ads.html', impressionTrackers: ['http://my.imp.tracker/url'], - javascriptTrackers: '', - title: 'This is an example Prebid Native creative' - } + javascriptTrackers: '', + title: 'This is an example Prebid Native creative', + }, }; beforeEach(function () {}); @@ -493,12 +783,508 @@ describe('validate native', function () { it('should accept bid if no image sizes are defined', function () { decorateAdUnitsWithNativeParams([adUnit]); - const index = stubAuctionIndex({adUnits: [adUnit]}) - let result = nativeBidIsValid(validBid, {index}); + const index = stubAuctionIndex({ adUnits: [adUnit] }); + let result = nativeBidIsValid(validBid, { index }); expect(result).to.be.true; - result = nativeBidIsValid(noIconDimBid, {index}); + result = nativeBidIsValid(noIconDimBid, { index }); expect(result).to.be.true; - result = nativeBidIsValid(noImgDimBid, {index}); + result = nativeBidIsValid(noImgDimBid, { index }); expect(result).to.be.true; }); + + it('should convert from old-style native to OpenRTB request', () => { + const adUnit = { + transactionId: 'test_adunit', + mediaTypes: { + native: { + title: { + required: true, + }, + body: { + required: true, + len: 45 + }, + image: { + required: true, + sizes: [150, 50], + aspect_ratios: [{ + min_width: 150, + min_height: 50 + }] + }, + icon: { + required: true, + aspect_ratios: [{ + min_width: 150, + min_height: 50 + }] + }, + address: {}, + }, + }, + }; + + const ortb = toOrtbNativeRequest(adUnit.mediaTypes.native); + expect(ortb).to.be.a('object'); + expect(ortb.assets).to.be.a('array'); + + // title + expect(ortb.assets[0]).to.deep.include({ + id: 0, + required: 1, + title: { + len: 140 + } + }); + + // body => data + expect(ortb.assets[1]).to.deep.include({ + id: 1, + required: 1, + data: { + type: 2, + len: 45 + } + }); + + // image => image + expect(ortb.assets[2]).to.deep.include({ + id: 2, + required: 1, + img: { + type: 3, // Main Image + w: 150, + h: 50, + } + }); + + expect(ortb.assets[3]).to.deep.include({ + id: 3, + required: 1, + img: { + type: 1, // Icon Image + wmin: 150, + hmin: 50, + } + }); + + expect(ortb.assets[4]).to.deep.include({ + id: 4, + required: 0, + data: { + type: 9, + } + }); + }); + + ['bogusKey', 'clickUrl', 'privacyLink'].forEach(nativeKey => { + it(`should not generate an empty asset for key ${nativeKey}`, () => { + const ortbReq = toOrtbNativeRequest({ + [nativeKey]: { + required: true + } + }); + expect(ortbReq.assets.length).to.equal(0); + }); + }) + + it('should convert from ortb to old-style native request', () => { + const openRTBRequest = { + 'ver': '1.2', + 'context': 2, + 'contextsubtype': 20, + 'plcmttype': 11, + 'plcmtcnt': 1, + 'aurlsupport': 0, + 'privacy': 1, + 'eventrackers': [ + { + 'event': 1, + 'methods': [1, 2] + }, + { + 'event': 2, + 'methods': [1] + } + ], + 'assets': [ + { + 'id': 123, + 'required': 1, + 'title': { + 'len': 140 + } + }, + { + 'id': 128, + 'required': 0, + 'img': { + 'wmin': 836, + 'hmin': 627, + 'type': 3 + } + }, + { + 'id': 124, + 'required': 1, + 'img': { + 'wmin': 50, + 'hmin': 50, + 'type': 1 + } + }, + { + 'id': 126, + 'required': 1, + 'data': { + 'type': 1, + 'len': 25 + } + }, + { + 'id': 127, + 'required': 1, + 'data': { + 'type': 2, + 'len': 140 + } + } + ] + }; + + const oldNativeRequest = fromOrtbNativeRequest(openRTBRequest); + + expect(oldNativeRequest).to.be.a('object'); + expect(oldNativeRequest.title).to.include({ + required: true, + len: 140 + }); + + expect(oldNativeRequest.image).to.deep.include({ + required: false, + aspect_ratios: { + min_width: 836, + min_height: 627, + ratio_width: 836, + ratio_height: 627 + } + }); + + expect(oldNativeRequest.icon).to.deep.include({ + required: true, + aspect_ratios: { + min_width: 50, + min_height: 50, + ratio_width: 50, + ratio_height: 50 + } + }); + expect(oldNativeRequest.sponsoredBy).to.include({ + required: true, + len: 25 + }) + expect(oldNativeRequest.body).to.include({ + required: true, + len: 140 + }) + }); + + if (FEATURES.NATIVE) { + it('should convert ortb bid requests to proprietary requests', () => { + const validBidRequests = [{ + bidId: 'bidId3', + adUnitCode: 'adUnitCode3', + transactionId: 'transactionId3', + mediaTypes: { + banner: {} + }, + params: { + publisher: 'publisher2', + placement: 'placement3' + } + }]; + const resultRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + expect(resultRequests).to.be.deep.equals(validBidRequests); + + validBidRequests[0].mediaTypes.native = { + ortb: { + ver: '1.2', + context: 2, + contextsubtype: 20, + plcmttype: 11, + plcmtcnt: 1, + aurlsupport: 0, + privacy: 1, + eventrackers: [ + { + event: 1, + methods: [1, 2] + }, + { + event: 2, + methods: [1] + } + ], + assets: [ + { + id: 123, + required: 1, + title: { + len: 140 + } + }, + { + id: 128, + required: 0, + img: { + wmin: 836, + hmin: 627, + type: 3 + } + }, + { + id: 124, + required: 1, + img: { + wmin: 50, + hmin: 50, + type: 1 + } + }, + { + id: 126, + required: 1, + data: { + type: 1, + len: 25 + } + }, + { + id: 127, + required: 1, + data: { + type: 2, + len: 140 + } + } + ] + } + }; + + const resultRequests2 = convertOrtbRequestToProprietaryNative(validBidRequests); + expect(resultRequests2[0].mediaTypes.native).to.deep.include({ + title: { + required: true, + len: 140 + }, + icon: { + required: true, + aspect_ratios: { + min_width: 50, + min_height: 50, + ratio_width: 50, + ratio_height: 50 + } + }, + sponsoredBy: { + required: true, + len: 25 + }, + body: { + required: true, + len: 140 + } + }); + }); + } }); + +describe('legacyPropertiesToOrtbNative', () => { + describe('click trakckers', () => { + it('should convert clickUrl to link.url', () => { + const native = legacyPropertiesToOrtbNative({clickUrl: 'some-url'}); + expect(native.link.url).to.eql('some-url'); + }); + it('should convert single clickTrackers to link.clicktrackers', () => { + const native = legacyPropertiesToOrtbNative({clickTrackers: 'some-url'}); + expect(native.link.clicktrackers).to.eql([ + 'some-url' + ]) + }); + it('should convert multiple clickTrackers into link.clicktrackers', () => { + const native = legacyPropertiesToOrtbNative({clickTrackers: ['url1', 'url2']}); + expect(native.link.clicktrackers).to.eql([ + 'url1', + 'url2' + ]) + }) + }); + describe('impressionTrackers', () => { + it('should convert a single tracker into an eventtracker entry', () => { + const native = legacyPropertiesToOrtbNative({impressionTrackers: 'some-url'}); + expect(native.eventtrackers).to.eql([ + { + event: 1, + method: 1, + url: 'some-url' + } + ]); + }); + + it('should convert an array into corresponding eventtracker entries', () => { + const native = legacyPropertiesToOrtbNative({impressionTrackers: ['url1', 'url2']}); + expect(native.eventtrackers).to.eql([ + { + event: 1, + method: 1, + url: 'url1' + }, + { + event: 1, + method: 1, + url: 'url2' + } + ]) + }) + }); + describe('javascriptTrackers', () => { + it('should convert a single value into jstracker', () => { + const native = legacyPropertiesToOrtbNative({javascriptTrackers: 'some-markup'}); + expect(native.jstracker).to.eql('some-markup'); + }) + it('should merge multiple values into a single jstracker', () => { + const native = legacyPropertiesToOrtbNative({javascriptTrackers: ['some-markup', 'some-other-markup']}); + expect(native.jstracker).to.eql('some-markupsome-other-markup'); + }) + }); +}); + +describe('fireImpressionTrackers', () => { + let runMarkup, fetchURL; + beforeEach(() => { + runMarkup = sinon.stub(); + fetchURL = sinon.stub(); + }) + + function runTrackers(resp) { + fireImpressionTrackers(resp, {runMarkup, fetchURL}) + } + + it('should run markup in jstracker', () => { + runTrackers({ + jstracker: 'some-markup' + }); + sinon.assert.calledWith(runMarkup, 'some-markup'); + }); + + it('should fetch each url in imptrackers', () => { + const urls = ['url1', 'url2']; + runTrackers({ + imptrackers: urls + }); + urls.forEach(url => sinon.assert.calledWith(fetchURL, url)); + }); + + it('should fetch each url in eventtrackers that use the image method', () => { + const urls = ['url1', 'url2']; + runTrackers({ + eventtrackers: urls.map(url => ({event: 1, method: 1, url})) + }); + urls.forEach(url => sinon.assert.calledWith(fetchURL, url)) + }); + + it('should load as a script each url in eventtrackers that use the js method', () => { + const urls = ['url1', 'url2']; + runTrackers({ + eventtrackers: urls.map(url => ({event: 1, method: 2, url})) + }); + urls.forEach(url => sinon.assert.calledWith(runMarkup, sinon.match(`script async src="${url}"`))) + }); + + it('should not fire trackers that are not impression trakcers', () => { + runTrackers({ + link: { + clicktrackers: ['click-url'] + }, + eventtrackers: [{ + event: 2, // not imp + method: 1, + url: 'some-url' + }] + }); + sinon.assert.notCalled(fetchURL); + sinon.assert.notCalled(runMarkup); + }) +}) + +describe('fireClickTrackers', () => { + let fetchURL; + beforeEach(() => { + fetchURL = sinon.stub(); + }); + + function runTrackers(resp, assetId = null) { + fireClickTrackers(resp, assetId, {fetchURL}); + } + + it('should load each URL in link.clicktrackers', () => { + const urls = ['url1', 'url2']; + runTrackers({ + link: { + clicktrackers: urls + } + }); + urls.forEach(url => sinon.assert.calledWith(fetchURL, url)); + }) + + it('should load each URL in asset.link.clicktrackers, when response is ORTB', () => { + const urls = ['asset_url1', 'asset_url2']; + runTrackers({ + assets: [ + { + id: 1, + link: { + clicktrackers: urls + } + } + ], + }, 1); + urls.forEach(url => sinon.assert.calledWith(fetchURL, url)); + }) +}) + +describe('toOrtbNativeResponse', () => { + it('should work when there are unrequested assets in the response', () => { + const legacyResponse = { + 'title': 'vtitle', + 'body': 'vbody' + } + const request = toOrtbNativeRequest({ + title: { + required: 'true' + }, + + }); + const ortbResponse = toOrtbNativeResponse(legacyResponse, request); + expect(ortbResponse.assets.length).to.eql(1); + }); + + it('should not modify the request', () => { + const legacyResponse = { + title: 'vtitle' + } + const request = toOrtbNativeRequest({ + title: { + required: true + } + }); + const requestCopy = JSON.parse(JSON.stringify(request)); + const response = toOrtbNativeResponse(legacyResponse, request); + expect(request).to.eql(requestCopy); + sinon.assert.match(response.assets[0], { + title: { + text: 'vtitle' + } + }) + }) +}) diff --git a/test/spec/refererDetection_spec.js b/test/spec/refererDetection_spec.js index a404e4f883e..3f33d1646a1 100644 --- a/test/spec/refererDetection_spec.js +++ b/test/spec/refererDetection_spec.js @@ -1,6 +1,6 @@ -import { detectReferer } from 'src/refererDetection.js'; -import { config } from 'src/config.js'; -import { expect } from 'chai'; +import {detectReferer, ensureProtocol, parseDomain} from 'src/refererDetection.js'; +import {config} from 'src/config.js'; +import {expect} from 'chai'; /** * Build a walkable linked list of window-like objects for testing. @@ -11,13 +11,13 @@ import { expect } from 'chai'; * @param {boolean} [ancestorOrigins] * @returns {Object} */ -function buildWindowTree(urls, topReferrer = '', canonicalUrl = null, ancestorOrigins = false) { +function buildWindowTree(urls, topReferrer = null, canonicalUrl = null, ancestorOrigins = false) { /** - * Find the origin from a given fully-qualified URL. - * - * @param {string} url The fully qualified URL - * @returns {string|null} - */ + * Find the origin from a given fully-qualified URL. + * + * @param {string} url The fully qualified URL + * @returns {string|null} + */ function getOrigin(url) { const originRegex = new RegExp('^(https?://[^/]+/?)'); @@ -30,83 +30,89 @@ function buildWindowTree(urls, topReferrer = '', canonicalUrl = null, ancestorOr return null; } - let previousWindow; - const myOrigin = getOrigin(urls[urls.length - 1]); + const inaccessibles = []; - const windowList = urls.map((url, index) => { - const theirOrigin = getOrigin(url), - sameOrigin = (myOrigin === theirOrigin); - - const win = {}; - - if (sameOrigin) { - win.location = { - href: url - }; + let previousWindow, topWindow; + const topOrigin = getOrigin(urls[0]); - if (ancestorOrigins) { - win.location.ancestorOrigins = urls.slice(0, index).reverse().map(getOrigin); + const windowList = urls.map((url, index) => { + const thisOrigin = getOrigin(url), + sameOriginAsPrevious = index === 0 ? true : (getOrigin(urls[index - 1]) === thisOrigin), + sameOriginAsTop = thisOrigin === topOrigin; + + const win = { + location: { + href: url, + }, + document: { + referrer: index === 0 ? topReferrer : urls[index - 1] } - - if (index === 0) { - win.document = { - referrer: topReferrer - }; - - if (canonicalUrl) { - win.document.querySelector = function(selector) { - if (selector === "link[rel='canonical']") { - return { - href: canonicalUrl - }; - } - - return null; + }; + + if (topWindow == null) { + topWindow = win; + win.document.querySelector = function (selector) { + if (selector === 'link[rel=\'canonical\']') { + return { + href: canonicalUrl }; } - } else { - win.document = { - referrer: urls[index - 1] - }; - } + return null; + }; } + if (sameOriginAsPrevious) { + win.parent = previousWindow; + } else { + win.parent = inaccessibles[inaccessibles.length - 1]; + } + if (ancestorOrigins) { + win.location.ancestorOrigins = urls.slice(0, index).reverse().map(getOrigin); + } + win.top = sameOriginAsTop ? topWindow : inaccessibles[0]; + + const inWin = {parent: inaccessibles[inaccessibles.length - 1], top: inaccessibles[0]}; + if (index === 0) { + inWin.top = inWin; + } + ['document', 'location'].forEach((prop) => { + Object.defineProperty(inWin, prop, { + get: function () { + throw new Error('cross-origin access'); + } + }); + }); + inaccessibles.push(inWin); previousWindow = win; return win; }); - const topWindow = windowList[0]; - - previousWindow = null; - - windowList.forEach((win) => { - win.top = topWindow; - win.parent = previousWindow || topWindow; - previousWindow = win; - }); - return windowList[windowList.length - 1]; } describe('Referer detection', () => { + afterEach(function () { + config.resetConfig(); + }); + describe('Non cross-origin scenarios', () => { describe('No iframes', () => { - afterEach(function () { - config.resetConfig(); - }); - it('Should return the current window location and no canonical URL', () => { const testWindow = buildWindowTree(['https://example.com/some/page'], 'https://othersite.com/'), result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page', + sinon.assert.match(result, { + topmostLocation: 'https://example.com/some/page', + location: 'https://example.com/some/page', reachedTop: true, isAmp: false, numIframes: 0, stack: ['https://example.com/some/page'], - canonicalUrl: null + canonicalUrl: null, + page: 'https://example.com/some/page', + ref: 'https://othersite.com/', + domain: 'example.com', }); }); @@ -114,13 +120,17 @@ describe('Referer detection', () => { const testWindow = buildWindowTree(['https://example.com/some/page'], 'https://othersite.com/', 'https://example.com/canonical/page'), result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page', + sinon.assert.match(result, { + topmostLocation: 'https://example.com/some/page', + location: 'https://example.com/some/page', reachedTop: true, isAmp: false, numIframes: 0, stack: ['https://example.com/some/page'], - canonicalUrl: 'https://example.com/canonical/page' + canonicalUrl: 'https://example.com/canonical/page', + page: 'https://example.com/canonical/page', + ref: 'https://othersite.com/', + domain: 'example.com' }); }); }); @@ -130,8 +140,9 @@ describe('Referer detection', () => { const testWindow = buildWindowTree(['https://example.com/some/page', 'https://example.com/other/page', 'https://example.com/third/page'], 'https://othersite.com/'), result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page', + sinon.assert.match(result, { + topmostLocation: 'https://example.com/some/page', + location: 'https://example.com/some/page', reachedTop: true, isAmp: false, numIframes: 2, @@ -140,7 +151,10 @@ describe('Referer detection', () => { 'https://example.com/other/page', 'https://example.com/third/page' ], - canonicalUrl: null + canonicalUrl: null, + page: 'https://example.com/some/page', + ref: 'https://othersite.com/', + domain: 'example.com' }); }); @@ -148,8 +162,9 @@ describe('Referer detection', () => { const testWindow = buildWindowTree(['https://example.com/some/page', 'https://example.com/other/page', 'https://example.com/third/page'], 'https://othersite.com/', 'https://example.com/canonical/page'), result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page', + sinon.assert.match(result, { + topmostLocation: 'https://example.com/some/page', + location: 'https://example.com/some/page', reachedTop: true, isAmp: false, numIframes: 2, @@ -158,18 +173,22 @@ describe('Referer detection', () => { 'https://example.com/other/page', 'https://example.com/third/page' ], - canonicalUrl: 'https://example.com/canonical/page' + canonicalUrl: 'https://example.com/canonical/page', + page: 'https://example.com/canonical/page', + ref: 'https://othersite.com/', + domain: 'example.com' }); }); - it('Should override canonical URL with config pageUrl', () => { - config.setConfig({'pageUrl': 'testUrl.com'}); + it('Should override canonical URL (and page) with config pageUrl', () => { + config.setConfig({'pageUrl': 'https://testurl.com'}); const testWindow = buildWindowTree(['https://example.com/some/page', 'https://example.com/other/page', 'https://example.com/third/page'], 'https://othersite.com/', 'https://example.com/canonical/page'), result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page', + sinon.assert.match(result, { + topmostLocation: 'https://example.com/some/page', + location: 'https://example.com/some/page', reachedTop: true, isAmp: false, numIframes: 2, @@ -178,7 +197,10 @@ describe('Referer detection', () => { 'https://example.com/other/page', 'https://example.com/third/page' ], - canonicalUrl: 'testUrl.com' + canonicalUrl: 'https://testurl.com', + page: 'https://testurl.com', + ref: 'https://othersite.com/', + domain: 'testurl.com' }); }); }); @@ -189,8 +211,9 @@ describe('Referer detection', () => { const testWindow = buildWindowTree(['https://example.com/some/page', 'https://safe.frame/ad'], 'https://othersite.com/', 'https://canonical.example.com/'), result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page', + sinon.assert.match(result, { + location: 'https://example.com/some/page', + topmostLocation: 'https://example.com/some/page', reachedTop: true, isAmp: false, numIframes: 1, @@ -198,7 +221,10 @@ describe('Referer detection', () => { 'https://example.com/some/page', 'https://safe.frame/ad' ], - canonicalUrl: null + canonicalUrl: null, + page: 'https://example.com/some/page', + ref: null, + domain: 'example.com' }); }); @@ -206,8 +232,9 @@ describe('Referer detection', () => { const testWindow = buildWindowTree(['https://example.com/some/page', 'https://safe.frame/ad', 'https://safe.frame/ad'], 'https://othersite.com/', 'https://canonical.example.com/'), result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page', + sinon.assert.match(result, { + topmostLocation: 'https://example.com/some/page', + location: 'https://example.com/some/page', reachedTop: true, isAmp: false, numIframes: 2, @@ -216,16 +243,20 @@ describe('Referer detection', () => { 'https://safe.frame/ad', 'https://safe.frame/ad' ], - canonicalUrl: null + canonicalUrl: null, + page: 'https://example.com/some/page', + ref: null, + domain: 'example.com', }); }); - it('Should return the second iframe location with three cross-origin windows and no ancessorOrigins', () => { + it('Should return the second iframe location with three cross-origin windows and no ancestorOrigins', () => { const testWindow = buildWindowTree(['https://example.com/some/page', 'https://safe.frame/ad', 'https://otherfr.ame/ad'], 'https://othersite.com/', 'https://canonical.example.com/'), result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://safe.frame/ad', + sinon.assert.match(result, { + topmostLocation: 'https://safe.frame/ad', + location: null, reachedTop: false, isAmp: false, numIframes: 2, @@ -234,16 +265,20 @@ describe('Referer detection', () => { 'https://safe.frame/ad', 'https://otherfr.ame/ad' ], - canonicalUrl: null + canonicalUrl: null, + page: null, + ref: null, + domain: null }); }); - it('Should return the top window origin with three cross-origin windows with ancessorOrigins', () => { + it('Should return the top window origin with three cross-origin windows with ancestorOrigins', () => { const testWindow = buildWindowTree(['https://example.com/some/page', 'https://safe.frame/ad', 'https://otherfr.ame/ad'], 'https://othersite.com/', 'https://canonical.example.com/', true), result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/', + sinon.assert.match(result, { + topmostLocation: 'https://example.com/', + location: 'https://example.com/', reachedTop: false, isAmp: false, numIframes: 2, @@ -252,7 +287,10 @@ describe('Referer detection', () => { 'https://safe.frame/ad', 'https://otherfr.ame/ad' ], - canonicalUrl: null + canonicalUrl: null, + page: 'https://example.com/', + ref: null, + domain: 'example.com' }); }); }); @@ -268,8 +306,9 @@ describe('Referer detection', () => { const result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page/amp/', + sinon.assert.match(result, { + location: 'https://example.com/some/page/amp/', + topmostLocation: 'https://example.com/some/page/amp/', reachedTop: true, isAmp: true, numIframes: 1, @@ -277,7 +316,10 @@ describe('Referer detection', () => { 'https://example.com/some/page/amp/', 'https://ad-iframe.ampproject.org/ad' ], - canonicalUrl: 'https://example.com/some/page/' + canonicalUrl: 'https://example.com/some/page/', + page: 'https://example.com/some/page/', + ref: null, + domain: 'example.com' }); }); @@ -291,8 +333,9 @@ describe('Referer detection', () => { const result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page/amp/', + sinon.assert.match(result, { + topmostLocation: 'https://example.com/some/page/amp/', + location: 'https://example.com/some/page/amp/', reachedTop: true, isAmp: true, numIframes: 1, @@ -300,10 +343,24 @@ describe('Referer detection', () => { 'https://example.com/some/page/amp/', 'https://ad-iframe.ampproject.org/ad' ], - canonicalUrl: 'https://example.com/some/page/' + canonicalUrl: 'https://example.com/some/page/', + page: 'https://example.com/some/page/', + ref: null, + domain: 'example.com' }); }); + it('should respect pageUrl as the primary source of canonicalUrl', () => { + config.setConfig({ + pageUrl: 'pub-defined' + }); + const w = buildWindowTree(['https://example.com', 'https://amp.com']); + w.context = { + canonicalUrl: 'should-be-overridden' + }; + expect(detectReferer(w)().canonicalUrl).to.equal('pub-defined'); + }); + describe('Cached AMP page in iframed search result', () => { it('Should return the AMP source and canonical URLs but with a null top-level stack location Without ancesorOrigins', () => { const testWindow = buildWindowTree(['https://google.com/amp/example-com/some/page/amp/', 'https://example-com.amp-cache.example.com/some/page/amp/', 'https://ad-iframe.ampproject.org/ad']); @@ -315,8 +372,9 @@ describe('Referer detection', () => { const result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page/amp/', + sinon.assert.match(result, { + topmostLocation: 'https://example.com/some/page/amp/', + location: 'https://example.com/some/page/amp/', reachedTop: false, isAmp: true, numIframes: 2, @@ -325,7 +383,10 @@ describe('Referer detection', () => { 'https://example.com/some/page/amp/', 'https://ad-iframe.ampproject.org/ad' ], - canonicalUrl: 'https://example.com/some/page/' + canonicalUrl: 'https://example.com/some/page/', + page: 'https://example.com/some/page/', + ref: null, + domain: 'example.com', }); }); @@ -339,8 +400,9 @@ describe('Referer detection', () => { const result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page/amp/', + sinon.assert.match(result, { + location: 'https://example.com/some/page/amp/', + topmostLocation: 'https://example.com/some/page/amp/', reachedTop: false, isAmp: true, numIframes: 2, @@ -349,7 +411,10 @@ describe('Referer detection', () => { 'https://example.com/some/page/amp/', 'https://ad-iframe.ampproject.org/ad' ], - canonicalUrl: 'https://example.com/some/page/' + canonicalUrl: 'https://example.com/some/page/', + page: 'https://example.com/some/page/', + ref: null, + domain: 'example.com' }); }); @@ -363,8 +428,9 @@ describe('Referer detection', () => { const result = detectReferer(testWindow)(); - expect(result).to.deep.equal({ - referer: 'https://example.com/some/page/amp/', + sinon.assert.match(result, { + location: 'https://example.com/some/page/amp/', + topmostLocation: 'https://example.com/some/page/amp/', reachedTop: false, isAmp: true, numIframes: 3, @@ -374,9 +440,105 @@ describe('Referer detection', () => { 'https://ad-iframe.ampproject.org/ad', 'https://ad-iframe.ampproject.org/ad' ], - canonicalUrl: 'https://example.com/some/page/' + canonicalUrl: 'https://example.com/some/page/', + page: 'https://example.com/some/page/', + ref: null, + domain: 'example.com', }); }); }); }); }); + +describe('ensureProtocol', () => { + ['', null, undefined].forEach((val) => { + it(`should return unchanged invalid input: ${val}`, () => { + expect(ensureProtocol(val)).to.eql(val); + }); + }); + + ['http:', 'https:'].forEach((protocol) => { + Object.entries({ + 'window.top.location.protocol': { + top: { + location: { + protocol + } + }, + location: { + protocol: 'unused' + } + }, + 'window.location.protocol': (() => { + const w = { + top: {}, + location: { + protocol + } + }; + Object.defineProperty(w.top, 'location', { + get: function () { + throw new Error('cross-origin'); + } + }); + return w; + })(), + }).forEach(([t, win]) => { + describe(`when ${t} declares ${protocol}`, () => { + Object.entries({ + 'declared': { + url: 'proto://example.com/page', + expect: 'proto://example.com/page' + }, + 'relative': { + url: '//example.com/page', + expect: `${protocol}//example.com/page` + }, + 'missing': { + url: 'example.com/page', + expect: `${protocol}//example.com/page` + } + }).forEach(([t, {url, expect: expected}]) => { + it(`should handle URLs with ${t} protocols`, () => { + expect(ensureProtocol(url, win)).to.equal(expected); + }); + }); + }); + }); + }); +}); + +describe('parseDomain', () => { + Object.entries({ + 'www.example.com': 'www.example.com', + 'example.com:443': 'example.com:443', + 'www.sub.example.com': 'www.sub.example.com', + 'example.com/page': 'example.com', + 'www.example.com:443/page': 'www.example.com:443', + 'http://www.example.com:443/page?query=value': 'www.example.com:443', + '': undefined, + }).forEach(([input, expected]) => { + it(`should extract domain from '${input}' -> '${expected}`, () => { + expect(parseDomain(input)).to.equal(expected); + }); + }); + Object.entries({ + 'www.example.com': 'example.com', + 'https://www.sub.example.com': 'sub.example.com', + '//www.example.com:443': 'example.com:443', + 'without.www.example.com': 'without.www.example.com' + }).forEach(([input, expected]) => { + it('should remove leading www if requested', () => { + expect(parseDomain(input, {noLeadingWww: true})).to.equal(expected); + }) + }); + Object.entries({ + 'example.com:443': 'example.com', + 'https://sub.example.com': 'sub.example.com', + 'http://sub.example.com:8443': 'sub.example.com' + }).forEach(([input, expected]) => { + it('should remove port if requested', () => { + expect(parseDomain(input, {noPort: true})).to.equal(expected); + }) + }) +}); diff --git a/test/spec/renderer_spec.js b/test/spec/renderer_spec.js index 6de06606136..c41334f916a 100644 --- a/test/spec/renderer_spec.js +++ b/test/spec/renderer_spec.js @@ -107,6 +107,21 @@ describe('Renderer', function () { sinon.assert.calledOnce(func2); expect(testRenderer1.cmd.length).to.equal(0); }); + + it('renders immediately when requested', function () { + const testRenderer3 = Renderer.install({ + url: 'https://httpbin.org/post', + config: { test: 'config2' }, + id: 2, + renderNow: true + }); + const func1 = sinon.spy(); + const testArg = 'testArgument'; + + testRenderer3.setRender(func1); + testRenderer3.render(testArg); + func1.calledWith(testArg).should.be.ok; + }); }); describe('3rd party renderer', function () { diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 88beaa88a67..6e37da4e85f 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -1671,25 +1671,29 @@ describe('adapterManager tests', function () { }) }); - it('should add nativeParams to adUnits after BEFORE_REQUEST_BIDS', () => { - function beforeReqBids(adUnits) { - adUnits.forEach(adUnit => { - adUnit.mediaTypes.native = { - type: 'image', - } - }) - } - events.on(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); - adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); - events.off(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); - expect(adUnits.map((u) => u.nativeParams).some(i => i == null)).to.be.false; - }); + if (FEATURES.NATIVE) { + it('should add nativeParams to adUnits after BEFORE_REQUEST_BIDS', () => { + function beforeReqBids(adUnits) { + adUnits.forEach(adUnit => { + adUnit.mediaTypes.native = { + type: 'image', + } + }) + } + + events.on(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); + adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ); + events.off(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); + expect(adUnits.map((u) => u.nativeParams).some(i => i == null)).to.be.false; + }); + } it('should make separate bidder request objects for each bidder', () => { adUnits = [utils.deepClone(getAdUnits()[0])]; @@ -1711,6 +1715,83 @@ describe('adapterManager tests', function () { expect(sizes1).not.to.deep.equal(sizes2); }); + it('should make FPD available under `ortb2`', () => { + const global = { + k1: 'v1', + k2: { + k3: 'v3', + k4: 'v4' + } + }; + const bidder = { + 'appnexus': { + ka: 'va', + k2: { + k3: 'override', + k5: 'v5' + } + } + }; + const requests = Object.fromEntries( + adapterManager.makeBidRequests(adUnits, 123, 'auction-id', 123, [], {global, bidder}) + .map((r) => [r.bidderCode, r]) + ); + sinon.assert.match(requests, { + rubicon: { + ortb2: global + }, + appnexus: { + ortb2: { + k1: 'v1', + ka: 'va', + k2: { + k3: 'override', + k4: 'v4', + k5: 'v5', + } + } + } + }); + requests.rubicon.bids.forEach((bid) => expect(bid.ortb2).to.eql(requests.rubicon.ortb2)); + requests.appnexus.bids.forEach((bid) => expect(bid.ortb2).to.eql(requests.appnexus.ortb2)); + }); + + describe('when calling the s2s adapter', () => { + beforeEach(() => { + config.setConfig({ + s2sConfig: { + enabled: true, + adapter: 'mockS2S', + bidders: ['appnexus'] + } + }) + adapterManager.bidderRegistry.mockS2S = { + callBids: sinon.stub() + }; + }); + afterEach(() => { + config.resetConfig(); + delete adapterManager.bidderRegistry.mockS2S; + }) + + it('should pass FPD', () => { + const ortb2Fragments = {}; + const req = { + bidderCode: 'appnexus', + src: CONSTANTS.S2S.SRC, + adUnitsS2SCopy: adUnits, + bids: [{ + bidder: 'appnexus', + src: CONSTANTS.S2S.SRC + }] + }; + adapterManager.callBids(adUnits, [req], sinon.stub(), sinon.stub(), {request: sinon.stub(), done: sinon.stub()}, 1000, sinon.stub(), ortb2Fragments); + sinon.assert.calledWith(adapterManager.bidderRegistry.mockS2S.callBids, sinon.match({ + ortb2Fragments: sinon.match.same(ortb2Fragments) + })); + }); + }) + describe('setBidderSequence', function () { beforeEach(function () { sinon.spy(utils, 'shuffle'); @@ -1734,6 +1815,64 @@ describe('adapterManager tests', function () { }); }); + describe('fledgeEnabled', function () { + const origRunAdAuction = navigator?.runAdAuction; + before(function () { + // navigator.runAdAuction doesn't exist, so we can't stub it normally with + // sinon.stub(navigator, 'runAdAuction') or something + navigator.runAdAuction = sinon.stub(); + }); + + after(function() { + navigator.runAdAuction = origRunAdAuction; + }) + + afterEach(function () { + config.resetConfig(); + }); + + it('should set fledgeEnabled correctly per bidder', function () { + config.setConfig({bidderSequence: 'fixed'}) + config.setBidderConfig({ + bidders: ['appnexus'], + config: { + fledgeEnabled: true, + } + }); + + const adUnits = [{ + 'code': '/19968336/header-bid-tag1', + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + }, + }, + 'bids': [ + { + 'bidder': 'appnexus', + }, + { + 'bidder': 'rubicon', + }, + ] + }]; + + const bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + expect(bidRequests[0].bids[0].bidder).equals('appnexus'); + expect(bidRequests[0].fledgeEnabled).to.be.true; + + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[1].fledgeEnabled).to.be.undefined; + }); + }); + describe('sizeMapping', function () { let sandbox; beforeEach(function () { diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 20a545a51d4..7164e468e71 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -12,6 +12,7 @@ import {hook} from '../../../../src/hook.js'; import {auctionManager} from '../../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../../helpers/indexStub.js'; import { bidderSettings } from '../../../../src/bidderSettings.js'; +import {decorateAdUnitsWithNativeParams} from '../../../../src/native.js'; const CODE = 'sampleBidder'; const MOCK_BIDS_REQUEST = { @@ -39,6 +40,10 @@ function onTimelyResponseStub() { } +before(() => { + hook.ready(); +}); + let wrappedCallback = config.callbackWithBidder(CODE); describe('bidders created by newBidder', function () { @@ -47,10 +52,6 @@ describe('bidders created by newBidder', function () { let addBidResponseStub; let doneStub; - before(() => { - hook.ready(); - }); - beforeEach(function () { spec = { code: CODE, @@ -874,85 +875,89 @@ describe('validate bid response: ', function () { indexStub.restore; }); - it('should add native bids that do have required assets', function () { - adUnits = [{ - transactionId: 'au', - nativeParams: { - title: {'required': true}, - } - }] - let bidRequest = { - bids: [{ - bidId: '1', - auctionId: 'first-bid-id', - adUnitCode: 'mock/placement', + if (FEATURES.NATIVE) { + it('should add native bids that do have required assets', function () { + adUnits = [{ transactionId: 'au', - params: { - param: 5 - }, - mediaType: 'native', + nativeParams: { + title: {'required': true}, + } }] - }; + decorateAdUnitsWithNativeParams(adUnits); + let bidRequest = { + bids: [{ + bidId: '1', + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + transactionId: 'au', + params: { + param: 5 + }, + mediaType: 'native', + }] + }; - let bids1 = Object.assign({}, - bids[0], - { - 'mediaType': 'native', - 'native': { - 'title': 'Native Creative', - 'clickUrl': 'https://www.link.example', + let bids1 = Object.assign({}, + bids[0], + { + 'mediaType': 'native', + 'native': { + 'title': 'Native Creative', + 'clickUrl': 'https://www.link.example', + } } - } - ); + ); - const bidder = newBidder(spec); + const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - expect(logErrorSpy.callCount).to.equal(0); - }); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + expect(logErrorSpy.callCount).to.equal(0); + }); - it('should not add native bids that do not have required assets', function () { - adUnits = [{ - transactionId: 'au', - nativeParams: { - title: {'required': true}, - }, - }]; - let bidRequest = { - bids: [{ - bidId: '1', - auctionId: 'first-bid-id', - adUnitCode: 'mock/placement', + it('should not add native bids that do not have required assets', function () { + adUnits = [{ transactionId: 'au', - params: { - param: 5 + nativeParams: { + title: {'required': true}, }, - mediaType: 'native', - }] - }; - let bids1 = Object.assign({}, - bids[0], - { - bidderCode: CODE, - mediaType: 'native', - native: { - title: undefined, - clickUrl: 'https://www.link.example', + }]; + decorateAdUnitsWithNativeParams(adUnits); + let bidRequest = { + bids: [{ + bidId: '1', + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + transactionId: 'au', + params: { + param: 5 + }, + mediaType: 'native', + }] + }; + let bids1 = Object.assign({}, + bids[0], + { + bidderCode: CODE, + mediaType: 'native', + native: { + title: undefined, + clickUrl: 'https://www.link.example', + } } - } - ); + ); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(false); - expect(logErrorSpy.callCount).to.equal(1); - }); + expect(addBidResponseStub.calledOnce).to.equal(false); + expect(logErrorSpy.calledWithMatch('Ignoring bid: Native bid missing some required properties.')).to.equal(true); + }); + } it('should add bid when renderer is present on outstream bids', function () { adUnits = [{ @@ -1045,8 +1050,8 @@ describe('validate bid response: ', function () { bids1 = Object.assign({}, bids[0], { - bidderCode: 'validAlternateBidder', - adapterCode: 'knownAdapter1' + bidderCode: 'validalternatebidder', + adapterCode: 'knownadapter1' } ); logWarnSpy = sinon.spy(utils, 'logWarn'); @@ -1073,16 +1078,14 @@ describe('validate bid response: ', function () { expect(logWarnSpy.callCount).to.equal(1); }); - it('should accept the bid, when allowAlternateBidderCodes flag is undefined (default should be true)', function () { + it('should reject the bid, when allowAlternateBidderCodes flag is undefined (default should be false)', function () { bidderSettingStub.returns(undefined); const bidder = newBidder(spec); spec.interpretResponse.returns(bids1); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(logWarnSpy.callCount).to.equal(0); - expect(logErrorSpy.callCount).to.equal(0); + expect(addBidResponseStub.calledOnce).to.equal(false); }); it('should log warning when the particular bidder is not specified in allowedAlternateBidderCodes and allowAlternateBidderCodes flag is true', function () { @@ -1123,6 +1126,31 @@ describe('validate bid response: ', function () { expect(logErrorSpy.callCount).to.equal(0); }); + it('should accept the bid, when allowedAlternateBidderCodes is marked as * (with space) and allowAlternateBidderCodes flag is true', function () { + bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); + bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns([' * ']); + + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(logWarnSpy.callCount).to.equal(0); + expect(logErrorSpy.callCount).to.equal(0); + }); + + it('should not accept the bid, when allowedAlternateBidderCodes is marked as empty array and allowAlternateBidderCodes flag is true', function () { + bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); + bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns([]); + + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(addBidResponseStub.calledOnce).to.equal(false); + expect(logWarnSpy.callCount).to.equal(1); + }); + it('should accept the bid, when allowedAlternateBidderCodes contains bidder name and allowAlternateBidderCodes flag is true', function () { bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns(['validAlternateBidder']); @@ -1158,6 +1186,70 @@ describe('validate bid response: ', function () { expect(addBidResponseStub.calledOnce).to.equal(false); expect(logWarnSpy.callCount).to.equal(1); }); + }); + + describe('when interpretResponse returns BidderAuctionResponse', function() { + const bidRequest = { + bids: [{ + bidId: '1', + bidder: CODE, + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + transactionId: 'au', + }] + }; + const fledgeAuctionConfig = { + bidId: '1', + } + describe('when response has FLEDGE auction config', function() { + let logInfoSpy; + + beforeEach(function () { + logInfoSpy = sinon.spy(utils, 'logInfo'); + }); + + afterEach(function () { + logInfoSpy.restore(); + }); + + it('should unwrap bids', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: bids, + fledgeAuctionConfigs: [] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + }); + + it('should call fledgeManager with FLEDGE configs', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: bids, + fledgeAuctionConfigs: [fledgeAuctionConfig] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(logInfoSpy.calledOnce).to.equal(true); + expect(logInfoSpy.firstCall.args[1]).to.equal(fledgeAuctionConfig); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + }) + + it('should call fledgeManager with FLEDGE configs even if no bids returned', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: [], + fledgeAuctionConfigs: [fledgeAuctionConfig] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(logInfoSpy.calledOnce).to.equal(true); + expect(logInfoSpy.firstCall.args[1]).to.equal(fledgeAuctionConfig); + expect(addBidResponseStub.calledOnce).to.equal(false); + }) + }) }) }); diff --git a/test/spec/unit/core/storageManager_spec.js b/test/spec/unit/core/storageManager_spec.js index 74a3b3b023f..cb38aed9e47 100644 --- a/test/spec/unit/core/storageManager_spec.js +++ b/test/spec/unit/core/storageManager_spec.js @@ -3,7 +3,7 @@ import { getCoreStorageManager, storageCallbacks, getStorageManager, - newStorageManager + newStorageManager, validateStorageEnforcement } from 'src/storageManager.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; @@ -55,6 +55,33 @@ describe('storage manager', function() { deviceAccessSpy.restore(); }); + describe(`core storage`, () => { + let storage, validateHook; + + beforeEach(() => { + storage = getCoreStorageManager(); + validateHook = sinon.stub().callsFake(function (next, ...args) { + next.apply(this, args); + }); + validateStorageEnforcement.before(validateHook, 99); + }); + + afterEach(() => { + validateStorageEnforcement.getHooks({hook: validateHook}).remove(); + config.resetConfig(); + }) + + it('should respect (vendorless) consent enforcement', () => { + storage.localStorageIsEnabled(); + expect(validateHook.args[0][1]).to.eql(true); // isVendorless should be set to true + }); + + it('should respect the deviceAccess flag', () => { + config.setConfig({deviceAccess: false}); + expect(storage.localStorageIsEnabled()).to.be.false + }) + }) + describe('localstorage forbidden access in 3rd-party context', function() { let errorLogSpy; let originalLocalStorage; @@ -108,8 +135,8 @@ describe('storage manager', function() { }); describe('when bidderSettings.allowStorage is defined', () => { - const DENIED_BIDDER = 'denied-bidder'; - const DENY_KEY = 'storageAllowed'; + const ALLOWED_BIDDER = 'allowed-bidder'; + const ALLOW_KEY = 'storageAllowed'; const COOKIE = 'test-cookie'; const LS_KEY = 'test-localstorage'; @@ -117,8 +144,8 @@ describe('storage manager', function() { function mockBidderSettings() { return { get(bidder, key) { - if (bidder === DENIED_BIDDER && key === DENY_KEY) { - return false; + if (bidder === ALLOWED_BIDDER && key === ALLOW_KEY) { + return true; } else { return undefined; } @@ -127,8 +154,8 @@ describe('storage manager', function() { } Object.entries({ - disallowed: [DENIED_BIDDER, false], - allowed: ['allowed-bidder', true] + disallowed: ['denied_bidder', false], + allowed: [ALLOWED_BIDDER, true] }).forEach(([test, [bidderCode, shouldWork]]) => { describe(`for ${test} bidders`, () => { let mgr; diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 53aa3b90fa8..4585ddbfaaf 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -599,7 +599,9 @@ describe('targeting tests', function () { } }); const defaultKeys = new Set(Object.values(CONSTANTS.DEFAULT_TARGETING_KEYS)); - Object.values(CONSTANTS.NATIVE_KEYS).forEach((k) => defaultKeys.add(k)); + if (FEATURES.NATIVE) { + Object.values(CONSTANTS.NATIVE_KEYS).forEach((k) => defaultKeys.add(k)); + } const expectedKeys = new Set(); bidsReceived @@ -802,26 +804,28 @@ describe('targeting tests', function () { expect(targeting['/123456/header-bid-tag-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_rubicon']).to.deep.equal(targeting['/123456/header-bid-tag-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]); }); - it('ensures keys are properly generated when enableSendAllBids is true and multiple bidders use native', function() { - const nativeAdUnitCode = '/19968336/prebid_native_example_1'; - enableSendAllBids = true; + if (FEATURES.NATIVE) { + it('ensures keys are properly generated when enableSendAllBids is true and multiple bidders use native', function () { + const nativeAdUnitCode = '/19968336/prebid_native_example_1'; + enableSendAllBids = true; - // update mocks for this test to return native bids - amBidsReceivedStub.callsFake(function() { - return [nativeBid1, nativeBid2]; - }); - amGetAdUnitsStub.callsFake(function() { - return [nativeAdUnitCode]; - }); + // update mocks for this test to return native bids + amBidsReceivedStub.callsFake(function () { + return [nativeBid1, nativeBid2]; + }); + amGetAdUnitsStub.callsFake(function () { + return [nativeAdUnitCode]; + }); - let targeting = targetingInstance.getAllTargeting([nativeAdUnitCode]); - expect(targeting[nativeAdUnitCode].hb_native_image).to.equal(nativeBid1.native.image.url); - expect(targeting[nativeAdUnitCode].hb_native_linkurl).to.equal(nativeBid1.native.clickUrl); - expect(targeting[nativeAdUnitCode].hb_native_title).to.equal(nativeBid1.native.title); - expect(targeting[nativeAdUnitCode].hb_native_image_dgad).to.exist.and.to.equal(nativeBid2.native.image.url); - expect(targeting[nativeAdUnitCode].hb_pb_dgads).to.exist.and.to.equal(nativeBid2.pbMg); - expect(targeting[nativeAdUnitCode].hb_native_body_appne).to.exist.and.to.equal(nativeBid1.native.body); - }); + let targeting = targetingInstance.getAllTargeting([nativeAdUnitCode]); + expect(targeting[nativeAdUnitCode].hb_native_image).to.equal(nativeBid1.native.image.url); + expect(targeting[nativeAdUnitCode].hb_native_linkurl).to.equal(nativeBid1.native.clickUrl); + expect(targeting[nativeAdUnitCode].hb_native_title).to.equal(nativeBid1.native.title); + expect(targeting[nativeAdUnitCode].hb_native_image_dgad).to.exist.and.to.equal(nativeBid2.native.image.url); + expect(targeting[nativeAdUnitCode].hb_pb_dgads).to.exist.and.to.equal(nativeBid2.pbMg); + expect(targeting[nativeAdUnitCode].hb_native_body_appne).to.exist.and.to.equal(nativeBid1.native.body); + }); + } it('does not include adpod type bids in the getBidsReceived results', function () { let adpodBid = utils.deepClone(bid1); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 6a7c79fe49d..3cee2b6b679 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -16,9 +16,11 @@ import * as auctionModule from 'src/auction.js'; import { registerBidder } from 'src/adapters/bidderFactory.js'; import { _sendAdToCreative } from 'src/secureCreatives.js'; import {find} from 'src/polyfill.js'; -import {synchronizePromise} from '../../helpers/syncPromise.js'; -import 'src/prebid.js'; +import * as pbjsModule from 'src/prebid.js'; import {hook} from '../../../src/hook.js'; +import {reset as resetDebugging} from '../../../src/debugging.js'; +import $$PREBID_GLOBAL$$ from 'src/prebid.js'; +import {resetAuctionState} from 'src/auction.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -33,7 +35,6 @@ require('modules/appnexusBidAdapter'); var config = require('test/fixtures/config.json'); -$$PREBID_GLOBAL$$ = $$PREBID_GLOBAL$$ || {}; var adUnits = getAdUnits(); var adUnitCodes = getAdUnits().map(unit => unit.code); var bidsBackHandler = function() {}; @@ -193,22 +194,21 @@ window.apntag = { } describe('Unit: Prebid Module', function () { - let bidExpiryStub, promiseSandbox; + let bidExpiryStub before(() => { hook.ready(); $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); + resetDebugging(); }); beforeEach(function () { - promiseSandbox = sinon.createSandbox(); - synchronizePromise(promiseSandbox); bidExpiryStub = sinon.stub(filters, 'isBidNotExpired').callsFake(() => true); configObj.setConfig({ useBidCache: true }); + resetAuctionState(); }); afterEach(function() { - promiseSandbox.restore(); $$PREBID_GLOBAL$$.adUnits = []; bidExpiryStub.restore(); configObj.setConfig({ useBidCache: false }); @@ -218,6 +218,51 @@ describe('Unit: Prebid Module', function () { auctionManager.clearAllAuctions(); }); + describe('and global adUnits', () => { + const startingAdUnits = [ + { + code: 'one', + }, + { + code: 'two', + } + ]; + let actualAdUnits, hookRan, done; + + function deferringHook(next, req) { + setTimeout(() => { + actualAdUnits = req.adUnits || $$PREBID_GLOBAL$$.adUnits; + done(); + }); + } + + beforeEach(() => { + $$PREBID_GLOBAL$$.requestBids.before(deferringHook, 99); + $$PREBID_GLOBAL$$.adUnits.splice(0, $$PREBID_GLOBAL$$.adUnits.length, ...startingAdUnits); + hookRan = new Promise((resolve) => { + done = resolve; + }); + }); + + afterEach(() => { + $$PREBID_GLOBAL$$.requestBids.getHooks({hook: deferringHook}).remove(); + $$PREBID_GLOBAL$$.adUnits.splice(0, $$PREBID_GLOBAL$$.adUnits.length); + }) + + Object.entries({ + 'addAdUnits': (g) => g.addAdUnits({code: 'three'}), + 'removeAdUnit': (g) => g.removeAdUnit('one') + }).forEach(([method, op]) => { + it(`once called, should not be affected by ${method}`, () => { + $$PREBID_GLOBAL$$.requestBids({}); + op($$PREBID_GLOBAL$$); + return hookRan.then(() => { + expect(actualAdUnits).to.eql(startingAdUnits); + }) + }); + }); + }); + describe('getAdserverTargetingForAdUnitCodeStr', function () { beforeEach(function () { resetAuction(); @@ -1605,8 +1650,124 @@ describe('Unit: Prebid Module', function () { assert.ok(spyExecuteCallback.calledOnce, 'callback executed when bidRequests is empty'); }); }); + + describe('starts auction', () => { + let startAuctionStub; + function saHook(fn, ...args) { + return startAuctionStub(...args); + } + beforeEach(() => { + startAuctionStub = sinon.stub(); + pbjsModule.startAuction.before(saHook); + configObj.resetConfig(); + }); + afterEach(() => { + pbjsModule.startAuction.getHooks({hook: saHook}).remove(); + }) + after(() => { + configObj.resetConfig(); + }); + + it('passing global and auction-level FPD as ortb2Fragments.global', () => { + configObj.setConfig({ + ortb2: { + 'k1': 'v1', + 'k2': { + 'k3': 'v3', + 'k4': 'v4' + } + } + }); + $$PREBID_GLOBAL$$.requestBids({ + ortb2: { + 'k5': 'v5', + 'k2': { + 'k3': 'override', + 'k7': 'v7' + } + } + }); + sinon.assert.calledWith(startAuctionStub, sinon.match({ + ortb2Fragments: { + global: { + 'k1': 'v1', + 'k5': 'v5', + 'k2': { + 'k3': 'override', + 'k4': 'v4', + 'k7': 'v7' + } + } + } + })) + }); + it('passing bidder-specific FPD as ortb2Fragments.bidder', () => { + configObj.setBidderConfig({ + bidders: ['bidderA', 'bidderC'], + config: { + ortb2: { + k1: 'v1' + } + } + }); + configObj.setBidderConfig({ + bidders: ['bidderB'], + config: { + ortb2: { + k2: 'v2' + } + } + }); + $$PREBID_GLOBAL$$.requestBids({}); + sinon.assert.calledWith(startAuctionStub, sinon.match({ + ortb2Fragments: { + bidder: { + bidderA: { + k1: 'v1' + }, + bidderB: { + k2: 'v2' + }, + bidderC: { + k1: 'v1' + } + } + } + })); + }); + }); }); + describe('startAuction', () => { + let sandbox, newAuctionStub; + beforeEach(() => { + sandbox = sinon.createSandbox(); + newAuctionStub = sandbox.stub(auctionManager, 'createAuction').callsFake(() => ({ + getAuctionId: () => 'mockAuctionId', + callBids: sinon.stub() + })); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('passes ortb2 fragments to createAuction', () => { + const ortb2Fragments = {}; + pbjsModule.startAuction({ + adUnits: [{ + code: 'au', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + bids: [{bidder: 'bd'}] + }], + ortb2Fragments + }); + sinon.assert.calledWith(newAuctionStub, sinon.match({ + ortb2Fragments: sinon.match.same(ortb2Fragments) + })); + }); + }) + describe('requestBids', function () { var adUnitsBackup; var auctionManagerStub; @@ -1680,6 +1841,29 @@ describe('Unit: Prebid Module', function () { .and.to.match(/[a-f0-9\-]{36}/i); }); + it('should always set ortb2.ext.tid same as transactionId in adUnits', function () { + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [ + { + code: 'test1', + mediaTypes: { banner: { sizes: [] } }, + bids: [] + }, { + code: 'test2', + mediaTypes: { banner: { sizes: [] } }, + bids: [] + } + ] + }); + + expect(auctionArgs.adUnits[0]).to.have.property('transactionId'); + expect(auctionArgs.adUnits[0]).to.have.property('ortb2Imp'); + expect(auctionArgs.adUnits[0].transactionId).to.equal(auctionArgs.adUnits[0].ortb2Imp.ext.tid); + expect(auctionArgs.adUnits[1]).to.have.property('transactionId'); + expect(auctionArgs.adUnits[1]).to.have.property('ortb2Imp'); + expect(auctionArgs.adUnits[1].transactionId).to.equal(auctionArgs.adUnits[1].ortb2Imp.ext.tid); + }); + it('should notify targeting of the latest auction for each adUnit', function () { let latestStub = sinon.stub(targeting, 'setLatestAuctionForAdUnit'); let getAuctionStub = sinon.stub(auction, 'getAuctionId').returns(2); @@ -1973,59 +2157,61 @@ describe('Unit: Prebid Module', function () { expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist; assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.')); - let badNativeImgSize = [{ - code: 'testb4', - bids: [], - mediaTypes: { - native: { - image: { - sizes: '300x250' + if (FEATURES.NATIVE) { + let badNativeImgSize = [{ + code: 'testb4', + bids: [], + mediaTypes: { + native: { + image: { + sizes: '300x250' + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badNativeImgSize - }); - expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; - assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.')); - - let badNativeImgAspRat = [{ - code: 'testb5', - bids: [], - mediaTypes: { - native: { - image: { - aspect_ratios: '300x250' + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badNativeImgSize + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.')); + + let badNativeImgAspRat = [{ + code: 'testb5', + bids: [], + mediaTypes: { + native: { + image: { + aspect_ratios: '300x250' + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badNativeImgAspRat - }); - expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; - assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.')); - - let badNativeIcon = [{ - code: 'testb6', - bids: [], - mediaTypes: { - native: { - icon: { - sizes: '300x250' + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badNativeImgAspRat + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.')); + + let badNativeIcon = [{ + code: 'testb6', + bids: [], + mediaTypes: { + native: { + icon: { + sizes: '300x250' + } } } - } - }]; - $$PREBID_GLOBAL$$.requestBids({ - adUnits: badNativeIcon - }); - expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.be.undefined; - expect(auctionArgs.adUnits[0].mediaTypes.native.icon).to.exist; - assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.')); + }]; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: badNativeIcon + }); + expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.be.undefined; + expect(auctionArgs.adUnits[0].mediaTypes.native.icon).to.exist; + assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.')); + } }); it('should throw error message and remove adUnit if adUnit.bids is not defined correctly', function () { @@ -2058,6 +2244,9 @@ describe('Unit: Prebid Module', function () { }); describe('multiformat requests', function () { + if (!FEATURES.NATIVE) { + return; + } let adUnits; beforeEach(function () { @@ -2109,6 +2298,9 @@ describe('Unit: Prebid Module', function () { }); describe('part 2', function () { + if (!FEATURES.NATIVE) { + return; + } let spyCallBids; let createAuctionStub; let adUnits; @@ -2176,7 +2368,7 @@ describe('Unit: Prebid Module', function () { it('splits native type to individual native assets', function () { let adUnits = [{ code: 'adUnit-code', - mediaTypes: { native: { type: 'image' } }, + mediaTypes: {native: {type: 'image'}}, bids: [ {bidder: 'appnexus', params: {placementId: 'id'}} ] @@ -2184,14 +2376,47 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.requestBids({adUnits}); const spyArgs = adapterManager.callBids.getCall(0); const nativeRequest = spyArgs.args[1][0].bids[0].nativeParams; - expect(nativeRequest).to.deep.equal({ - image: {required: true}, - title: {required: true}, - sponsoredBy: {required: true}, - clickUrl: {required: true}, - body: {required: false}, - icon: {required: false}, - }); + expect(nativeRequest.ortb.assets).to.deep.equal([ + { + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }, + { + required: 1, + id: 2, + title: { + len: 140, + } + }, + { + required: 1, + id: 3, + data: { + type: 1, + } + }, + { + required: 0, + id: 4, + data: { + type: 2, + } + }, + { + required: 0, + id: 5, + img: { + type: 1, + wmin: 20, + hmin: 20, + } + }, + ]); resetAuction(); }); }); diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index e3dc21ffd92..7d5f9af35dd 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -1,7 +1,6 @@ import { _sendAdToCreative, getReplier, receiveMessage } from 'src/secureCreatives.js'; -import * as secureCreatives from 'src/secureCreatives.js'; import * as utils from 'src/utils.js'; import {getAdUnits, getBidRequests, getBidResponses} from 'test/fixtures/fixtures.js'; import {auctionManager} from 'src/auctionManager.js'; @@ -164,6 +163,7 @@ describe('secureCreatives', () => { stubGetAllAssetsMessage.restore(); stubEmit.restore(); resetAuction(); + adResponse.adId = bidId; }); describe('Prebid Request', function() { @@ -308,36 +308,11 @@ describe('secureCreatives', () => { }); describe('Prebid Native', function() { - it('Prebid native should render', function () { - pushBidResponseToAuction({}); - - const data = { - adId: bidId, - message: 'Prebid Native', - action: 'allAssetRequest' - }; - - const ev = makeEvent({ - data: JSON.stringify(data), - source: { - postMessage: sinon.stub() - }, - origin: 'any origin' - }); - - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); - }); + if (!FEATURES.NATIVE) { + return; + } - it('Prebid native should allow stale rendering without config', function () { + it('Prebid native should render', function () { pushBidResponseToAuction({}); const data = { @@ -361,31 +336,17 @@ describe('secureCreatives', () => { sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); sinon.assert.calledOnce(ev.source.postMessage); sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); - - resetHistories(ev.source.postMessage); - - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); }); - it('Prebid native should allow stale rendering with config', function () { - configObj.setConfig({'auctionOptions': {'suppressStaleRender': true}}); - - pushBidResponseToAuction({}); + it('Prebid native should not fire BID_WON when receiveMessage is called more than once', () => { + let adId = 3; + pushBidResponseToAuction({ adId }); const data = { - adId: bidId, + adId: adId, message: 'Prebid Native', action: 'allAssetRequest' }; @@ -399,37 +360,18 @@ describe('secureCreatives', () => { }); receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); - - resetHistories(ev.source.postMessage); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); - - configObj.setConfig({'auctionOptions': {}}); + stubEmit.withArgs(CONSTANTS.EVENTS.BID_WON, adResponse).calledOnce; }); it('Prebid native should fire trackers', function () { - pushBidResponseToAuction({}); + let adId = 2; + pushBidResponseToAuction({adId}); const data = { - adId: bidId, + adId: adId, message: 'Prebid Native', action: 'click', }; @@ -446,8 +388,8 @@ describe('secureCreatives', () => { sinon.assert.neverCalledWith(spyLogWarn, warning); sinon.assert.calledOnce(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); resetHistories(ev.source.postMessage); @@ -457,8 +399,8 @@ describe('secureCreatives', () => { sinon.assert.neverCalledWith(spyLogWarn, warning); sinon.assert.calledOnce(stubFireNativeTrackers); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); - sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); + sinon.assert.notCalled(spyAddWinningBid); expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); }); diff --git a/test/spec/unit/utils/promise_spec.js b/test/spec/unit/utils/promise_spec.js index ee2d55bf9a5..a931b8bc9c4 100644 --- a/test/spec/unit/utils/promise_spec.js +++ b/test/spec/unit/utils/promise_spec.js @@ -1,49 +1,294 @@ -import {promiseControls} from '../../../../src/utils/promise.js'; +import {GreedyPromise, defer} from '../../../../src/utils/promise.js'; -describe('promiseControls', () => { - function lazyControls() { - // NOTE: here we are testing that calling resolve / reject works correctly regardless of whether the - // browser runs promise resolvers before / after returning control to the code; e.g. with the following: - // - // new Promise(() => console.log('1')); console.log('2') - // - // it seems that the browser will output '1', then '2' - but is it always guaranteed to do so? - // it's not clear from MDN (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) - // so here we make sure it works in both cases. - - return promiseControls({ - promiseFactory: (r) => ({ - then: function () { - const p = new Promise(r); - return p.then.apply(p, arguments); +describe('GreedyPromise', () => { + it('throws when resolver is not a function', () => { + expect(() => new GreedyPromise()).to.throw(); + }) + + Object.entries({ + 'resolved': (use) => new GreedyPromise((resolve) => use(resolve)), + 'rejected': (use) => new GreedyPromise((_, reject) => use(reject)) + }).forEach(([t, makePromise]) => { + it(`runs callbacks immediately when ${t}`, () => { + let cbRan = false; + const cb = () => { cbRan = true }; + let resolver; + makePromise((fn) => { resolver = fn }).then(cb, cb); + resolver(); + expect(cbRan).to.be.true; + }) + }); + + describe('unhandled rejections', () => { + let unhandled, done, stop; + + function reset(expectUnhandled) { + let pending = expectUnhandled; + let resolver; + unhandled.reset(); + unhandled.callsFake(() => { + pending--; + if (pending === 0) { + resolver(); } }) - }) - } + done = new Promise((resolve) => { + resolver = resolve; + stop = function () { + if (expectUnhandled === 0) { + resolve() + } else { + resolver = resolve; + } + } + }) + } + + before(() => { + unhandled = sinon.stub(); + window.addEventListener('unhandledrejection', unhandled); + }); + + after(() => { + window.removeEventListener('unhandledrejection', unhandled); + }); + + function getUnhandledErrors() { + return unhandled.args.map((args) => args[0].reason); + } + + Object.entries({ + 'simple reject': [1, (P) => { P.reject('err'); stop() }], + 'caught reject': [0, (P) => P.reject('err').catch((e) => { stop(); return e })], + 'unhandled reject with finally': [1, (P) => P.reject('err').finally(() => 'finally')], + 'error handler that throws': [1, (P) => P.reject('err').catch((e) => { stop(); throw e })], + 'rejection handled later in the chain': [0, (P) => P.reject('err').then((v) => v).catch((e) => { stop(); return e })], + 'multiple errors in one chain': [1, (P) => P.reject('err').then((v) => v).catch((e) => e).then((v) => { stop(); return P.reject(v) })], + 'multiple errors in one chain, all handled': [0, (P) => P.reject('err').then((v) => v).catch((e) => e).then((v) => P.reject(v)).catch((e) => { stop(); return e })], + 'separate chains for rejection and handling': [1, (P) => { + const p = P.reject('err'); + p.catch((e) => { stop(); return e; }) + p.then((v) => v); + }], + 'separate rejections merged without handling': [2, (P) => { + const p1 = P.reject('err1'); + const p2 = P.reject('err2'); + p1.then(() => p2).finally(stop); + }], + 'separate rejections merged for handling': [0, (P) => { + const p1 = P.reject('err1'); + const p2 = P.reject('err2'); + P.all([p1, p2]).catch((e) => { stop(); return e }); + }], + // eslint-disable-next-line no-throw-literal + 'exception in resolver': [1, (P) => new P(() => { stop(); throw 'err'; })], + // eslint-disable-next-line no-throw-literal + 'exception in resolver, caught': [0, (P) => new P(() => { throw 'err' }).catch((e) => { stop(); return e })], + 'errors from nested promises': [1, (P) => new P((resolve) => setTimeout(() => { resolve(P.reject('err')); stop(); }))], + 'errors from nested promises, caught': [0, (P) => new P((resolve) => setTimeout(() => resolve(P.reject('err')))).catch((e) => { stop(); return e })], + }).forEach(([t, [expectUnhandled, op]]) => { + describe(`on ${t}`, () => { + it('should match vanilla Promises', () => { + let vanillaUnhandled; + reset(expectUnhandled); + op(Promise); + return done.then(() => { + vanillaUnhandled = getUnhandledErrors(); + reset(expectUnhandled); + op(GreedyPromise); + return done; + }).then(() => { + const actualUnhandled = getUnhandledErrors(); + expect(actualUnhandled.length).to.eql(expectUnhandled); + expect(actualUnhandled).to.eql(vanillaUnhandled); + }) + }) + }) + }); + }); + + describe('idioms', () => { + let makePromise, pendingFailure, pendingSuccess; + + Object.entries({ + // eslint-disable-next-line no-throw-literal + 'resolver that throws': (P) => new P(() => { throw 'error' }), + 'resolver that resolves multiple times': (P) => new P((resolve) => { resolve('first'); resolve('second'); }), + 'resolver that rejects multiple times': (P) => new P((resolve, reject) => { reject('first'); reject('second') }), + 'resolver that resolves and rejects': (P) => new P((resolve, reject) => { reject('first'); resolve('second') }), + 'resolver that resolves with multiple arguments': (P) => new P((resolve) => resolve('one', 'two')), + 'resolver that rejects with multiple arguments': (P) => new P((resolve, reject) => reject('one', 'two')), + 'resolver that resolves to a promise': (P) => new P((resolve) => resolve(makePromise(P, 'val'))), + 'resolver that resolves to a promise that resolves to a promise': (P) => new P((resolve) => resolve(makePromise(P, makePromise(P, 'val')))), + 'resolver that resolves to a rejected promise': (P) => new P((resolve) => resolve(makePromise(P, 'err', true))), + 'simple .then': (P) => makePromise(P, 'value').then((v) => `${v} and then`), + 'chained .then': (P) => makePromise(P, 'value').then((v) => makePromise(P, `${v} and then`)), + '.then with error handler': (P) => makePromise(P, 'err', true).then(null, (e) => `${e} and then`), + '.then with chained error handler': (P) => makePromise(P, 'err', true).then(null, (e) => makePromise(P, `${e} and then`)), + '.then that throws': (P) => makePromise(P, 'value').then((v) => { throw v }), + '.then that throws in error handler': (P) => makePromise(P, 'err', true).then(null, (e) => { throw e }), + '.then with no args': (P) => makePromise(P, 'value').then(), + '.then that rejects': (P) => makePromise(P, 'value').then((v) => P.reject(v)), + '.then that rejects in error handler': (P) => makePromise(P, 'err', true).then(null, (err) => P.reject(err)), + '.then with no error handler on a rejection': (P) => makePromise(P, 'err', true).then((v) => `resolved ${v}`), + '.then with no success handler on a resolution': (P) => makePromise(P, 'value').then(null, (e) => `caught ${e}`), + 'simple .catch': (P) => makePromise(P, 'err', true).catch((err) => `caught ${err}`), + 'identity .catch': (P) => makePromise(P, 'err', true).catch((err) => err).then((v) => v), + '.catch that throws': (P) => makePromise(P, 'err', true).catch((err) => { throw err }), + 'chained .catch': (P) => makePromise(P, 'err', true).catch((err) => makePromise(P, err)), + 'chained .catch that rejects': (P) => makePromise(P, 'err', true).catch((err) => P.reject(`reject with ${err}`)), + 'simple .finally': (P) => { + let fval; + return makePromise(P, 'value') + .finally(() => fval = 'finally ran') + .then((val) => `${val} ${fval}`) + }, + 'chained .finally': (P) => { + let fval; + return makePromise(P, 'value') + .finally(() => pendingSuccess.then(() => { fval = 'finally ran' })) + .then((val) => `${val} ${fval}`) + }, + '.finally on a rejection': (P) => { + let fval; + return makePromise(P, 'error', true) + .finally(() => { fval = 'finally' }) + .catch((err) => `${err} ${fval}`) + }, + 'chained .finally on a rejection': (P) => { + let fval; + return makePromise(P, 'error', true) + .finally(() => pendingSuccess.then(() => { fval = 'finally' })) + .catch((err) => `${err} ${fval}`) + }, + // eslint-disable-next-line no-throw-literal + '.finally that throws': (P) => makePromise(P, 'value').finally(() => { throw 'error' }), + 'chained .finally that rejects': (P) => makePromise(P, 'value').finally(() => P.reject('error')), + 'scalar Promise.resolve': (P) => P.resolve('scalar'), + 'null Promise.resolve': (P) => P.resolve(null), + 'chained Promise.resolve': (P) => P.resolve(pendingSuccess), + 'chained Promise.resolve on failure': (P) => P.resolve(pendingFailure), + 'scalar Promise.reject': (P) => P.reject('scalar'), + 'chained Promise.reject': (P) => P.reject(pendingSuccess), + 'chained Promise.reject on failure': (P) => P.reject(pendingFailure), + 'simple Promise.all': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two')]), + 'Promise.all with scalars': (P) => P.all([makePromise(P, 'one'), 'two']), + 'Promise.all with errors': (P) => P.all([makePromise(P, 'one'), makePromise(P, 'two'), makePromise(P, 'err', true)]), + 'Promise.allSettled': (P) => P.allSettled([makePromise(P, 'one', true), makePromise(P, 'two'), makePromise(P, 'three', true)]), + 'Promise.allSettled with scalars': (P) => P.allSettled([makePromise(P, 'value'), 'scalar']), + 'Promise.race that succeeds': (P) => P.race([makePromise(P, 'error', true, 10), makePromise(P, 'success')]), + 'Promise.race that fails': (P) => P.race([makePromise(P, 'success', false, 10), makePromise(P, 'error', true)]), + 'Promise.race with scalars': (P) => P.race(['scalar', makePromise(P, 'success')]), + }).forEach(([t, op]) => { + describe(t, () => { + describe('when mixed with deferrals', () => { + beforeEach(() => { + makePromise = function(ctor, value, fail = false, delay = 0) { + // eslint-disable-next-line new-cap + return new ctor((resolve, reject) => { + setTimeout(() => fail ? reject(value) : resolve(value), delay) + }) + }; + pendingSuccess = makePromise(Promise, 'pending result', false, 10); + pendingFailure = makePromise(Promise, 'pending failure', true, 10); + }); + + it(`behaves like vanilla promises`, () => { + const vanilla = op(Promise); + const greedy = op(GreedyPromise); + // note that we are not using `allSettled` & co to resolve our promises, + // to avoid transformations those methods do under the hood + const {actual = {}, expected = {}} = {}; + return new Promise((resolve) => { + let pending = 2; + function collect(dest, slot) { + return function (value) { + dest[slot] = value; + pending--; + if (pending === 0) { + resolve() + } + } + } + vanilla.then(collect(expected, 'success'), collect(expected, 'failure')); + greedy.then(collect(actual, 'success'), collect(actual, 'failure')); + }).then(() => { + expect(actual).to.eql(expected); + }); + }); + + it(`once resolved, runs callbacks immediately`, () => { + const promise = op(GreedyPromise).catch(() => null); + return promise.then(() => { + let cbRan = false; + promise.then(() => { cbRan = true }); + expect(cbRan).to.be.true; + }); + }); + }); + + describe('when all promises involved are greedy', () => { + beforeEach(() => { + makePromise = function(ctor, value, fail = false, delay = 0) { + // eslint-disable-next-line new-cap + return new ctor((resolve, reject) => { + const run = () => fail ? reject(value) : resolve(value); + delay === 0 ? run() : setTimeout(run, delay); + }) + }; + pendingSuccess = makePromise(GreedyPromise, 'pending result'); + pendingFailure = makePromise(GreedyPromise, 'pending failure', true); + }); + + it('resolves immediately', () => { + let cbRan = false; + op(GreedyPromise).catch(() => null).then(() => { cbRan = true }); + expect(cbRan).to.be.true; + }); + }); + }); + }); + }); + + describe('.timeout', () => { + const timeout = GreedyPromise.timeout; + + it('should resolve immediately when ms is 0', () => { + let cbRan = false; + timeout(0.0).then(() => { cbRan = true }); + expect(cbRan).to.be.true; + }); + + it('should schedule timeout on ms > 0', (done) => { + let cbRan = false; + timeout(5).then(() => { cbRan = true }); + expect(cbRan).to.be.false; + setTimeout(() => { + expect(cbRan).to.be.true; + done(); + }, 10) + }); + }); +}); + +describe('promiseControls', () => { Object.entries({ 'resolve': (p) => p, 'reject': (p) => p.then(() => 'wrong', (v) => v) }).forEach(([method, transform]) => { describe(method, () => { - Object.entries({ - 'before the resolver': lazyControls, - 'after the resolver': promiseControls, - }).forEach(([t, controls]) => { - describe(`when called ${t}`, () => { - it(`should ${method} the promise`, () => { - const ctl = controls(); - ctl[method]('result'); - return transform(ctl.promise).then((res) => expect(res).to.equal('result')); - }); + it(`should ${method} the promise`, () => { + const ctl = defer(); + ctl[method]('result'); + return transform(ctl.promise).then((res) => expect(res).to.equal('result')); + }); - it('should ignore calls after the first', () => { - const ctl = controls(); - ctl[method]('result'); - ctl[method]('other'); - return transform(ctl.promise).then((res) => expect(res).to.equal('result')); - }); - }) - }) - }) + it('should ignore calls after the first', () => { + const ctl = defer(); + ctl[method]('result'); + ctl[method]('other'); + return transform(ctl.promise).then((res) => expect(res).to.equal('result')); + }); + }); }); }); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 50a95b079c9..500f479355d 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1243,4 +1243,20 @@ describe('Utils', function () { }); }); }); + + describe('setScriptAttributes', () => { + it('correctly adds attributes from an object', () => { + const script = document.createElement('script'), + attrs = { + 'data-first_prop': '1', + 'data-second_prop': 'b', + 'id': 'newId' + }; + script.id = 'oldId'; + utils.setScriptAttributes(script, attrs); + expect(script.dataset['first_prop']).to.equal('1'); + expect(script.dataset.second_prop).to.equal('b'); + expect(script.id).to.equal('newId'); + }); + }); }); diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index 34e9bed04b6..a13028c966a 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; import {auctionManager} from '../../src/auctionManager.js'; import {AuctionIndex} from '../../src/auctionIndex.js'; +import { batchingCache } from '../../src/auction.js'; const should = chai.should(); @@ -29,8 +30,7 @@ function getMockBid(bidder, auctionId, bidderRequestId) { 'sizes': [300, 250], 'bidId': '123', 'bidderRequestId': bidderRequestId, - 'auctionId': auctionId, - 'storedAuctionResponse': 11111 + 'auctionId': auctionId }; } @@ -156,12 +156,12 @@ describe('The video cache', function () { puts: [{ type: 'xml', value: vastXml1, - ttlseconds: 25, + ttlseconds: 40, key: customKey1 }, { type: 'xml', value: vastXml2, - ttlseconds: 25, + ttlseconds: 40, key: customKey2 }] }; @@ -206,7 +206,7 @@ describe('The video cache', function () { puts: [{ type: 'xml', value: vastXml1, - ttlseconds: 25, + ttlseconds: 40, key: customKey1, bidid: '12345abc', aid: '1234-56789-abcde', @@ -214,7 +214,7 @@ describe('The video cache', function () { }, { type: 'xml', value: vastXml2, - ttlseconds: 25, + ttlseconds: 40, key: customKey2, bidid: 'cba54321', aid: '1234-56789-abcde', @@ -277,7 +277,7 @@ describe('The video cache', function () { puts: [{ type: 'xml', value: vastXml1, - ttlseconds: 25, + ttlseconds: 40, key: customKey1, bidid: '12345abc', bidder: 'appnexus', @@ -286,7 +286,7 @@ describe('The video cache', function () { }, { type: 'xml', value: vastXml2, - ttlseconds: 25, + ttlseconds: 40, key: customKey2, bidid: 'cba54321', bidder: 'rubicon', @@ -298,6 +298,35 @@ describe('The video cache', function () { JSON.parse(request.requestBody).should.deep.equal(payload); }); + it('should wait the duration of the batchTimeout and pass the correct batchSize if batched requests are enabled in the config', () => { + const mockAfterBidAdded = function() {}; + let callback = null; + let mockTimeout = sinon.stub().callsFake((cb) => { callback = cb }); + + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache', + batchSize: 3, + batchTimeout: 20 + } + }); + + let stubCache = sinon.stub(); + const batchAndStore = batchingCache(mockTimeout, stubCache); + for (let i = 0; i < 3; i++) { + batchAndStore({}, {}, mockAfterBidAdded); + } + + sinon.assert.calledOnce(mockTimeout); + sinon.assert.calledWith(mockTimeout, sinon.match.any, 20); + + const expectedBatch = [{ afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }]; + + callback(); + + sinon.assert.calledWith(stubCache, expectedBatch); + }); + function assertRequestMade(bid, expectedValue) { store([bid], function () { }); @@ -310,7 +339,7 @@ describe('The video cache', function () { puts: [{ type: 'xml', value: expectedValue, - ttlseconds: 25 + ttlseconds: 40 }], }); } diff --git a/test/test_deps.js b/test/test_deps.js index 77fbad93e1c..35713106f8c 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -4,6 +4,7 @@ window.process = { } }; +require('test/helpers/consentData.js'); require('test/helpers/prebidGlobal.js'); require('test/mocks/adloaderStub.js'); require('test/mocks/xhr.js'); diff --git a/webpack.conf.js b/webpack.conf.js index 5269f5300f5..0ead550e446 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -1,12 +1,34 @@ +const TerserPlugin = require('terser-webpack-plugin'); var prebid = require('./package.json'); var path = require('path'); var webpack = require('webpack'); var helpers = require('./gulpHelpers.js'); var { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); var argv = require('yargs').argv; +const fs = require('fs'); +const babelConfig = require('./babelConfig.js')({disableFeatures: helpers.getDisabledFeatures(), prebidDistUrlBase: argv.distUrlBase}); +const {WebpackManifestPlugin} = require('webpack-manifest-plugin') var plugins = [ - new webpack.EnvironmentPlugin({'LiveConnectMode': null}) + new webpack.EnvironmentPlugin({'LiveConnectMode': null}), + new WebpackManifestPlugin({ + fileName: 'dependencies.json', + generate: (seed, files) => { + const entries = new Set(); + const addEntry = entries.add.bind(entries); + + files.forEach(file => file.chunk && file.chunk._groups && file.chunk._groups.forEach(addEntry)); + + return Array.from(entries).reduce((acc, entry) => { + const name = (entry.options || {}).name || (entry.runtimeChunk || {}).name + const files = (entry.chunks || []) + .filter(chunk => chunk.name !== name) + .flatMap(chunk => [...chunk.files]) + .filter(Boolean); + return name && files.length ? {...acc, [`${name}.js`]: files} : acc + }, seed) + } + }) ]; if (argv.analyze) { @@ -28,15 +50,21 @@ module.exports = { const entry = { 'prebid-core': { import: './src/prebid.js' + }, + 'debugging-standalone': { + import: './modules/debugging/standalone.js' } }; const selectedModules = new Set(helpers.getArgModules()); + Object.entries(helpers.getModules()).forEach(([fn, mod]) => { if (selectedModules.size === 0 || selectedModules.has(mod)) { - entry[mod] = { + const moduleEntry = { import: fn, dependOn: 'prebid-core' - } + }; + + entry[mod] = moduleEntry; } }); return entry; @@ -53,7 +81,7 @@ module.exports = { use: [ { loader: 'babel-loader', - options: helpers.getAnalyticsOptions(), + options: Object.assign({}, babelConfig, helpers.getAnalyticsOptions()), } ] }, @@ -63,6 +91,7 @@ module.exports = { use: [ { loader: 'babel-loader', + options: babelConfig } ], } @@ -71,6 +100,40 @@ module.exports = { optimization: { usedExports: true, sideEffects: true, + minimizer: [ + new TerserPlugin({ + extractComments: false, // do not generate unhelpful LICENSE comment + terserOptions: { + module: true, // do not prepend every module with 'use strict'; allow mangling of top-level locals + } + }) + ], + splitChunks: { + chunks: 'initial', + minChunks: 1, + minSize: 0, + cacheGroups: (() => { + const libRoot = path.resolve(__dirname, 'libraries'); + const libraries = Object.fromEntries( + fs.readdirSync(libRoot) + .filter((f) => fs.lstatSync(path.resolve(libRoot, f)).isDirectory()) + .map(lib => { + const dir = path.resolve(libRoot, lib) + const def = { + name: lib, + test: (module) => { + return module.resource && module.resource.startsWith(dir) + } + } + return [lib, def]; + }) + ); + return Object.assign(libraries, { + default: false, + defaultVendors: false + }) + })() + } }, plugins };