diff --git a/.circleci/config.yml b/.circleci/config.yml index 784b520ecc1..7f9324956ca 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,18 +36,21 @@ aliases: - &run_endtoend_test name: BrowserStack End to end testing - command: echo "127.0.0.1 test.localhost" | sudo tee -a /etc/hosts && gulp e2e-test --host=test.localhost + 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 @@ -82,16 +87,6 @@ workflows: commit: jobs: - build - nightly: - triggers: - - schedule: - cron: "0 0 * * *" - filters: - branches: - only: - - master - jobs: - - e2etest experimental: pipelines: true diff --git a/.eslintrc.js b/.eslintrc.js index d3379d70919..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', @@ -45,12 +46,19 @@ module.exports = { 'no-throw-literal': 'off', 'no-undef': 2, 'no-useless-escape': 'off', - 'no-console': 'error' + 'no-console': 'error', }, overrides: Object.keys(allowedModules).map((key) => ({ files: key + '/**/*.js', rules: { - 'prebid/validate-imports': ['error', allowedModules[key]] + 'prebid/validate-imports': ['error', allowedModules[key]], + 'no-restricted-globals': [ + 'error', + { + name: 'require', + message: 'use import instead' + } + ] } })).concat([{ // code in other packages (such as plugins/eslint) is not "seen" by babel and its parser will complain. 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/issue_tracker.yml b/.github/workflows/issue_tracker.yml index 4397337b4c7..fa33ffe5c53 100644 --- a/.github/workflows/issue_tracker.yml +++ b/.github/workflows/issue_tracker.yml @@ -3,8 +3,13 @@ on: issues: types: - opened +permissions: + contents: read + jobs: track_issue: + permissions: + contents: none runs-on: ubuntu-latest steps: - name: Generate token 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/PR_REVIEW.md b/PR_REVIEW.md index 1152e2942bf..45ca30a7a3d 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -51,11 +51,16 @@ Follow steps above for general review process. In addition, please verify the fo - If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed. - All bidder parameter conventions must be followed: - Video params must be read from AdUnit.mediaTypes.video when available; however bidder config can override the ad unit. - - First party data must be read from [`fpd.context` and `fpd.user`](https://docs.prebid.org/dev-docs/publisher-api-reference.html#setConfig-fpd). + - First party data must be read from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd). - Adapters that accept a floor parameter must also support the [floors module](https://docs.prebid.org/dev-docs/modules/floors.html) -- look for a call to the `getFloor()` function. - Adapters cannot accept an schain parameter. Rather, they must look for the schain parameter at bidRequest.schain. - - The bidRequest page referrer must checked in addition to any bidder-specific parameter. + - The bidderRequest.refererInfo.referer must be checked in addition to any bidder-specific parameter. - If they're getting the COPPA flag, it must come from config.getConfig('coppa'); + - Page position must come from bidrequest.mediaTypes.banner.pos or bidrequest.mediaTypes.video.pos + - Global OpenRTB fields should come from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd): + - bcat, battr, badv + - Impression-specific OpenRTB fields should come from bidrequest.ortb2imp + - instl - Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/BIDDER.md file): - If they support the GDPR consentManagement module and TCF1, add `gdpr_supported: true` - If they support the GDPR consentManagement module and TCF2, add `tcf2_supported: true` @@ -123,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..11dc01e8f07 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,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 +193,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/babelConfig.js b/babelConfig.js index c1ddc11b689..785c6171c42 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,12 +18,12 @@ 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'), + [path.resolve(__dirname, './plugins/pbjsGlobals.js'), options], useLocal('babel-plugin-transform-object-assign'), ], } 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..60e2fab0df2 100644 --- a/gulpHelpers.js +++ b/gulpHelpers.js @@ -6,9 +6,12 @@ const MANIFEST = 'package.json'; const through = require('through2'); const _ = require('lodash'); const gutil = require('gulp-util'); -const submodules = require('./modules/.submodules.json'); +const dependencyMap = require('./modules/.submodules.json'); +const submodules = dependencyMap.parentModules; +const libraries = dependencyMap.libraries; const MODULE_PATH = './modules'; +const LIBRARY_PATH = './libraries'; const BUILD_PATH = './build/dist'; const DEV_PATH = './build/dev'; const ANALYTICS_PATH = '../analytics'; @@ -68,34 +71,68 @@ module.exports = { } }); + Object.keys(libraries).forEach(library => { + if (!modules.includes(library) && modules.some(module => libraries[library].dependants.includes(module))) { + modules.unshift(library); + } + }); + return modules; }, + getParentLibraries(moduleName) { + const libraryNames = []; + Object.keys(libraries).forEach(libraryName => { + const library = libraries[libraryName]; + if (library.dependants.includes(moduleName)) { + libraryNames.push(libraryName); + } + }); + return libraryNames; + }, + getLibraryFiles(name) { + const library = libraries[name]; + const files = library.files.map((file) => path.resolve('./libraries/', name, file)) + return files; + }, + isLibrary(name) { + return !!libraries[name]; + }, getModules: _.memoize(function(externalModules) { externalModules = externalModules || []; var internalModules; try { + var getInternalModules = function(absolutePath) { + return fs.readdirSync(absolutePath) + .filter(file => (/^[^\.]+(\.js)?$/).test(file)) + .reduce((memo, file) => { + var moduleName = file.split(new RegExp('[.\\' + path.sep + ']'))[0]; + var modulePath = path.join(absolutePath, file); + if (fs.lstatSync(modulePath).isDirectory()) { + modulePath = path.join(modulePath, 'index.js') + } + if (fs.existsSync(modulePath)) { + memo[modulePath] = moduleName; + } + return memo; + }, {}); + }; + var absoluteModulePath = path.join(__dirname, MODULE_PATH); - internalModules = fs.readdirSync(absoluteModulePath) - .filter(file => (/^[^\.]+(\.js)?$/).test(file)) - .reduce((memo, file) => { - var moduleName = file.split(new RegExp('[.\\' + path.sep + ']'))[0]; - var modulePath = path.join(absoluteModulePath, file); - if (fs.lstatSync(modulePath).isDirectory()) { - modulePath = path.join(modulePath, 'index.js') - } - if (fs.existsSync(modulePath)) { - memo[modulePath] = moduleName; - } - return memo; - }, {}); + var absoluteLibraryPath = path.join(__dirname, LIBRARY_PATH); + + internalModules = getInternalModules(absoluteModulePath); + var internalLibraries = getInternalModules(absoluteLibraryPath); + Object.assign(internalModules, internalLibraries); } catch (err) { internalModules = {}; } return Object.assign(externalModules.reduce((memo, module) => { try { // prefer internal project modules before looking at project dependencies - var modulePath = require.resolve(module, {paths: ['./modules']}); - if (modulePath === '') modulePath = require.resolve(module); + var modulePath = require.resolve(module, {paths: [MODULE_PATH, LIBRARY_PATH]}); + if (modulePath === '') { + modulePath = require.resolve(module); + } memo[modulePath] = module; } catch (err) { @@ -169,5 +206,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 ef51dacf45e..39181c92316 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 FAKE_SERVER_HOST = argv.host ? argv.host : 'localhost'; -const FAKE_SERVER_PORT = 4444; -const { spawn } = require('child_process'); +const INTEG_SERVER_HOST = argv.host ? argv.host : 'localhost'; +const INTEG_SERVER_PORT = 4444; +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,33 +139,40 @@ function makeDevpackPkg() { .pipe(connect.reload()); } -function makeWebpackPkg() { - var cloned = _.cloneDeep(webpackConfig); - delete cloned.devtool; +function makeWebpackPkg(extraConfig = {}) { + var cloned = _.merge(_.cloneDeep(webpackConfig), extraConfig); + if (!argv.sourceMaps) { + delete cloned.devtool; + } - var externalModules = helpers.getArgModules(); + return function buildBundle() { + var externalModules = helpers.getArgModules(); - const analyticsSources = helpers.getAnalyticsSources(); - const moduleSources = helpers.getModulePaths(externalModules); + 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(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid }))) - .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) { return bundle(dev).pipe(gulp.dest('build/' + (dev ? 'dev' : 'dist'))); } -function nodeBundle(modules) { +function nodeBundle(modules, dev = false) { return new Promise((resolve, reject) => { - bundle(false, modules) + bundle(dev, modules) .on('error', (err) => { reject(err); }) @@ -169,9 +183,51 @@ function nodeBundle(modules) { }); } +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); + const sm = dev || argv.sourceMaps; if (modules.length === 0) { modules = allModules.filter(module => explicitModules.indexOf(module) === -1); @@ -198,18 +254,11 @@ 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'))) - .pipe(gulpif(dev, sourcemaps.init({ loadMaps: true }))) + 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(dev, sourcemaps.write('.'))); + .pipe(gulpif(sm, sourcemaps.write('.'))); } // Run the unit tests. @@ -224,85 +273,76 @@ 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(); } else if (options.e2e) { - let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); - let wdioConf = path.join(__dirname, 'wdio.conf.js'); - let wdioOpts; - - if (options.file) { - wdioOpts = [ - wdioConf, - `--spec`, - `${options.file}` - ] - } else { - wdioOpts = [ - wdioConf - ]; - } - - // run fake-server - const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]); - fakeServer.stdout.on('data', (data) => { - console.log(`stdout: ${data}`); - }); - fakeServer.stderr.on('data', (data) => { - console.log(`stderr: ${data}`); - }); - - execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) + const integ = startIntegServer(); + startLocalServer(); + runWebdriver(options) .then(stdout => { // kill fake server - fakeServer.kill('SIGINT'); + integ.kill('SIGINT'); done(); process.exit(0); }) .catch(err => { // kill fake server - fakeServer.kill('SIGINT'); + integ.kill('SIGINT'); done(new Error(`Tests failed with error: ${err}`)); 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) } } } const test = testTaskMaker(); -function newKarmaCallback(done) { - return function (exitCode) { +function runWebdriver({file}) { + process.env.TEST_SERVER_HOST = argv.host || 'localhost'; + let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); + let wdioConf = path.join(__dirname, 'wdio.conf.js'); + let wdioOpts; + + if (file) { + wdioOpts = [ + wdioConf, + `--spec`, + `${file}` + ] + } else { + wdioOpts = [ + wdioConf + ]; + } + return execa(wdioCmd, wdioOpts, { stdio: 'inherit' }); +} + +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 @@ -323,41 +363,29 @@ function buildPostbid() { .pipe(gulp.dest('build/postbid/')); } -function setupE2e(done) { - if (!argv.host) { - throw new gutil.PluginError({ - plugin: 'E2E test', - message: gutil.colors.red('Host should be defined e.g. ap.localhost, anlocalhost. localhost cannot be used as safari browserstack is not able to connect to localhost') - }); - } - process.env.TEST_SERVER_HOST = argv.host; - if (argv.https) { - process.env.TEST_SERVER_PROTOCOL = argv.https; +function startIntegServer(dev = false) { + const args = ['./test/fake-server/index.js', `--port=${INTEG_SERVER_PORT}`, `--host=${INTEG_SERVER_HOST}`]; + if (dev) { + args.push('--dev=true') } - argv.e2e = true; - done(); -} - -function injectFakeServerEndpoint() { - return gulp.src(['build/dist/*.js']) - .pipe(replace('https://ib.adnxs.com/ut/v3/prebid', `http://${FAKE_SERVER_HOST}:${FAKE_SERVER_PORT}`)) - .pipe(gulp.dest('build/dist')); -} - -function injectFakeServerEndpointDev() { - return gulp.src(['build/dev/*.js']) - .pipe(replace('https://ib.adnxs.com/ut/v3/prebid', `http://${FAKE_SERVER_HOST}:${FAKE_SERVER_PORT}`)) - .pipe(gulp.dest('build/dev')); -} - -function startFakeServer() { - const fakeServer = spawn('node', ['./test/fake-server/index.js', `--port=${FAKE_SERVER_PORT}`]); - fakeServer.stdout.on('data', (data) => { + const srv = spawn('node', args); + srv.stdout.on('data', (data) => { console.log(`stdout: ${data}`); }); - fakeServer.stderr.on('data', (data) => { + srv.stderr.on('data', (data) => { console.log(`stderr: ${data}`); }); + return srv; +} + +function startLocalServer(options = {}) { + connect.server({ + https: argv.https, + port: port, + host: INTEG_SERVER_HOST, + root: './', + livereload: options.livereload + }); } // Watch Task with Live Reload @@ -373,13 +401,7 @@ function watchTaskMaker(options = {}) { 'modules/**/*.js', ].concat(options.alsoWatch)); - connect.server({ - https: argv.https, - port: port, - host: FAKE_SERVER_HOST, - root: './', - livereload: options.livereload - }); + startLocalServer(options); mainWatcher.on('all', options.task()); done(); @@ -398,11 +420,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, 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); @@ -414,12 +455,15 @@ 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-fake', gulp.series(clean, gulp.parallel('build-bundle-dev', watch), injectFakeServerEndpointDev, test, startFakeServer)); +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, makeWebpackPkg)); +gulp.task('default', gulp.series(clean, 'build-bundle-prod')); -gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-prod', watch), injectFakeServerEndpoint, test)); +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); gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step diff --git a/integrationExamples/gpt/1plusXRtdProviderExample.html b/integrationExamples/gpt/1plusXRtdProviderExample.html new file mode 100644 index 00000000000..2eb75063df1 --- /dev/null +++ b/integrationExamples/gpt/1plusXRtdProviderExample.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + +

1plusX RTD Module for Prebid

+ +
+ +
+ + + \ No newline at end of file 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_segments_example.html b/integrationExamples/gpt/akamaidap_segments_example.html index e85ac8e1337..f44c60292ce 100644 --- a/integrationExamples/gpt/akamaidap_segments_example.html +++ b/integrationExamples/gpt/akamaidap_segments_example.html @@ -68,6 +68,7 @@ } }, realTimeData: { + auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -76,9 +77,10 @@ apiHostname: "prebid.dap.akadns.net", apiVersion: "x1", domain: "prebid.org", - identityType: "dap-signature:1.0.0", - segtax: 503, - tokenTtl: 5, + identityType: "dap-signature:1.3.0", + segtax: 504, + dapEntropyUrl: 'https://dap-dist.akamaized.net/dapentropy.js', + dapEntropyTimeout: 1500 } } ] 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/blueconicRtdProvider_example.html b/integrationExamples/gpt/blueconicRtdProvider_example.html new file mode 100644 index 00000000000..e51a54b3be6 --- /dev/null +++ b/integrationExamples/gpt/blueconicRtdProvider_example.html @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + +

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/permutiveRtdProvider_example.html b/integrationExamples/gpt/permutiveRtdProvider_example.html index b6a22096c90..dbb4d2af0d6 100644 --- a/integrationExamples/gpt/permutiveRtdProvider_example.html +++ b/integrationExamples/gpt/permutiveRtdProvider_example.html @@ -45,6 +45,12 @@ } }, bids: [ + { + bidder: 'ix', + params: { + siteId: '123456', + } + }, { bidder: 'appnexus', params: { @@ -135,6 +141,7 @@ pbjs.que.push(function() { pbjs.setConfig({ debug: true, + pageUrl: 'http://www.test.com/test.html', realTimeData: { auctionDelay: 80, // maximum time for RTD modules to respond dataProviders: [ @@ -142,8 +149,20 @@ name: 'permutive', waitForIt: true, params: { - acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx'], + acBidders: ['appnexus', 'rubicon', 'ozone', 'trustx', 'ix'], maxSegs: 500, + transformations: [ + { + id: 'iab', + config: { + segtax: 4, + iabIds: { + 1000001: '777777', + 1000002: '888888' + } + } + } + ], overwrites: { rubicon: function (bid, data, acEnabled, utils, defaultFn) { if (defaultFn){ @@ -160,7 +179,7 @@ } }); pbjs.setBidderConfig({ - bidders: ['appnexus', 'rubicon'], + bidders: ['appnexus', 'rubicon', 'ix'], config: { ortb2: { site: { @@ -180,13 +199,9 @@ gender: 'm', keywords: 'a,b', data: [ - { - name: 'www.dataprovider1.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, - segment: [{ id: '687' }, { id: '123' }] - }, { name: 'permutive.com', + ext: { segtax: 6 }, segment: [{ id: '1' }] } ] diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html index e11a0b626c9..48b349649bc 100644 --- a/integrationExamples/gpt/userId_example.html +++ b/integrationExamples/gpt/userId_example.html @@ -77,6 +77,7 @@ "301": true, // zeotapIdPlus "91": true, // criteo "737": true, // amxId + "58": true, // 33acrossId } } } @@ -128,6 +129,17 @@ "expires": 30 } }, + { + "name": "33acrossId", + "params": { + "pid": '0' + }, + "storage": { + "type": 'html5', + "name": '33acrossId', + "expires": 90 + } + }, { "name": "intentIqId", "params": { @@ -239,14 +251,7 @@ { "name": "uid2" } - ,{ - name: "flocId", - params: { - // Default sharedid.org token : "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9" - // To get new token, register https://developer.chrome.com/origintrials/#/trials/active for Federated Learning of Cohorts - token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9" - } - }, + , { "name": "imuid", "params": { @@ -255,6 +260,9 @@ }, { "name": "dacId" + }, + { + "name": "gravitompId" } ], "syncDelay": 5000, diff --git a/integrationExamples/gpt/weboramaRtdProvider_example.html b/integrationExamples/gpt/weboramaRtdProvider_example.html index b81ec52b2c4..73843c49914 100644 --- a/integrationExamples/gpt/weboramaRtdProvider_example.html +++ b/integrationExamples/gpt/weboramaRtdProvider_example.html @@ -1,9 +1,10 @@ - + + weborama rtd submodule example @@ -26,9 +27,8 @@ params: { setPrebidTargeting: true, // optional sendToBidders: true, // optional - onData: function (data, site) { // optional - var kind = (site) ? 'site' : 'user'; - console.log('onData', kind, data); + onData: function (data, meta) { // optional + console.log('onData', data, meta); }, weboCtxConf: { token: "to-be-defined", // mandatory @@ -36,21 +36,30 @@ setPrebidTargeting: true, // override param.setPrebidTargeting or default true sendToBidders: true, // override param.sendToBidders or default true defaultProfile: { // optional - webo_ctx: ['moon'], + webo_ctx: ["Rugby_Renault_c11495", "Sport_c11893"], webo_ds: ['bar'] }, - //, onData: function (data, ...) { ...} + // enabled: false, + //, onData: function (data,...) { ...} }, weboUserDataConf: { - accountId: 12345, // optional + accountId: 12345, // recommended setPrebidTargeting: true, // override param.setPrebidTargeting or default true - sendToBidders: true, // override param.sendToBidders or default true + sendToBidders: ['smartadserver'], // specify the bidder to share data defaultProfile: { // optional - webo_cs: ['Red'], + webo_cs: ['red'], webo_audiences: ['bam'] }, localStorageProfileKey: 'webo_wam2gam_entry', // default + // enabled: false, //, onData: function (data,...) { ...} + }, + sfbxLiteDataConf: { + enabled: true, + defaultProfile: { // optional + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }, } } }] @@ -62,6 +71,9 @@ var div_1_sizes = [ [300, 300] ]; + var div_2_sizes = [ + [600, 100] + ]; var PREBID_TIMEOUT = 3000; var FAILSAFE_TIMEOUT = 5000; @@ -106,6 +118,46 @@ networkId: 456456, }, }] + }, + { + code: '/1056029/webo-wam-prebid', + mediaTypes: { + banner: { + sizes: div_2_sizes + } + }, + bids: [{ + bidder: 'smartadserver', + params: { + siteId: 1234, + pageId: 1234, + formatId: 1234, + } + }, { + bidder: 'pubmatic', + params: { + publisherId: '32572', + } + }, { + bidder: 'appnexus', + params: { + placementId: 234234, + } + }, { + bidder: 'rubicon', + params: { + accountId: '14062', + siteId: '70608', + zoneId: '335918', + userId: '12346', + } + }, { + bidder: 'criteo', + params: { + zoneId: 234234, + networkId: 456456, + }, + }] } ]; @@ -138,7 +190,6 @@ }); } - // in case PBJS doesn't load setTimeout(function () { initAdserver(); @@ -146,6 +197,7 @@ googletag.cmd.push(function () { googletag.defineSlot('/1056029/webo-ctx-prebid', div_1_sizes, 'div-gpt-ad-1620653642627-0').addService(googletag.pubads()); + googletag.defineSlot('/1056029/webo-wam-prebid', div_2_sizes, 'div-gpt-ad-1645023761875-0').addService(googletag.pubads()); googletag.pubads().disableInitialLoad(); googletag.enableServices(); }); @@ -154,11 +206,12 @@

- test webo ctx using prebid.js + test webo rtd submodule with prebid.js

Basic Prebid.js Example

Div-1
+
+ diff --git a/karma.conf.maker.js b/karma.conf.maker.js index b5c6b44e4fd..82ede90b141 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,7 +22,7 @@ function newWebpackConfig(codeCoverage) { .flatMap((r) => r.use) .filter((use) => use.loader === 'babel-loader') .forEach((use) => { - use.options = babelConfig(true); + use.options = babelConfig({test: true, disableFeatures}); }); if (codeCoverage) { @@ -92,7 +92,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 +119,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 +166,12 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { reporters: ['mocha'], + client: { + mocha: { + timeout: 3000 + } + }, + mochaReporter: { showDiff: true, output: 'minimal' 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/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 85e4658cc61..d24e7ff96f5 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -1,70 +1,88 @@ { - "userId": [ - "admixerIdSystem", - "adtelligentIdSystem", - "akamaiDAPIdSystem", - "amxIdSystem", - "britepoolIdSystem", - "connectIdSystem", - "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" - ], - "adpod": [ - "freeWheelAdserverVideo", - "dfpAdServerVideo" - ], - "rtdModule": [ - "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", + "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", + "optimeraRtdProvider", + "permutiveRtdProvider", + "reconciliationRtdProvider", + "sirdataRtdProvider", + "timeoutRtdProvider", + "weboramaRtdProvider" + ], + "fpdModule": [ + "enrichmentFpdModule", + "validationFpdModule" + ] + }, + "libraries": { + "getOrigin": { + "files": [ + "./index.js" + ], + "dependants": [ + "ooloAnalyticsAdapter", + "resetdigitalBidAdapter", + "rtbhouseBidAdapter.js" + ] + } + } +} \ No newline at end of file diff --git a/modules/1plusXRtdProvider.js b/modules/1plusXRtdProvider.js new file mode 100644 index 00000000000..5affafcf9d3 --- /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..700b1409da2 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); @@ -735,6 +738,7 @@ export const spec = { NON_MEASURABLE, code: BIDDER_CODE, + aliases: BIDDER_ALIASES, supportedMediaTypes: [ BANNER, VIDEO ], gvlid: GVLID, isBidRequestValid, diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js new file mode 100644 index 00000000000..3763fee5124 --- /dev/null +++ b/modules/33acrossIdSystem.js @@ -0,0 +1,115 @@ +/** + * This module adds 33acrossId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/33acrossIdSystem + * @requires module:modules/userId + */ + +import { logMessage, logError } from '../src/utils.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { uspDataHandler } from '../src/adapterManager.js'; + +const MODULE_NAME = '33acrossId'; +const API_URL = 'https://lexicon.33across.com/v1/envelope'; +const AJAX_TIMEOUT = 10000; + +function getEnvelope(response) { + if (!response.succeeded) { + logError(`${MODULE_NAME}: Unsuccessful response`); + + return; + } + + if (!response.data.envelope) { + logMessage(`${MODULE_NAME}: No envelope was received`); + + return; + } + + return response.data.envelope; +} + +function calculateQueryStringParams(pid, gdprConsentData) { + const uspString = uspDataHandler.getConsentData(); + const gdprApplies = Boolean(gdprConsentData?.gdprApplies); + const params = { + pid, + gdpr: Number(gdprApplies), + }; + + if (uspString) { + params.us_privacy = uspString; + } + + if (gdprApplies) { + params.gdpr_consent = gdprConsentData.consentString || ''; + } + + return params; +} + +/** @type {Submodule} */ +export const thirthyThreeAcrossIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + gvlid: 58, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {string} id + * @returns {{'33acrossId':{ envelope: string}}} + */ + decode(id) { + return { + [MODULE_NAME]: { + envelope: id + } + }; + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @returns {IdResponse|undefined} + */ + getId({ params = { } }, gdprConsentData) { + if (typeof params.pid !== 'string') { + logError(`${MODULE_NAME}: Submodule requires a partner ID to be defined`); + + return; + } + + const { pid, apiUrl = API_URL } = params; + + return { + callback(cb) { + ajaxBuilder(AJAX_TIMEOUT)(apiUrl, { + success(response) { + let envelope; + + try { + envelope = getEnvelope(JSON.parse(response)) + } catch (err) { + logError(`${MODULE_NAME}: ID reading error:`, err); + } + cb(envelope); + }, + error(err) { + logError(`${MODULE_NAME}: ID error response`, err); + + cb(); + } + }, calculateQueryStringParams(pid, gdprConsentData), { method: 'GET', withCredentials: true }); + } + }; + } +}; + +submodule('userId', thirthyThreeAcrossIdSubmodule); diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md new file mode 100644 index 00000000000..1e4af89344f --- /dev/null +++ b/modules/33acrossIdSystem.md @@ -0,0 +1,53 @@ +# 33ACROSS ID + +For help adding this submodule, please contact [PrebidUIM@33across.com](PrebidUIM@33across.com). + +### Prebid Configuration + +You can configure this submodule in your `userSync.userIds[]` configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [ + { + name: "33acrossId", + storage: { + name: "33acrossId", + type: "html5", + expires: 90, + refreshInSeconds: 8*3600 + }, + params: { + pid: "0010b00002GYU4eBAH", + }, + }, + ], + }, +}); +``` + +| Parameters under `userSync.userIds[]` | Scope | Type | Description | Example | +| ---| --- | --- | --- | --- | +| name | Required | String | Name for the 33Across ID submodule | `"33acrossId"` | | +| storage | Required | Object | Configures how to cache User IDs locally in the browser | See [storage settings](#storage-settings) | +| params | Required | Object | Parameters for 33Across ID submodule | See [params](#params) | + +### Storage Settings + +The following settings are available for the `storage` property in the `userSync.userIds[]` object: + +| Param name | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String| Name of the cookie or HTML5 local storage where the user ID will be stored | `"33acrossId"` | +| type | Required | String | `"html5"` (preferred) or `"cookie"` | `"html5"` | +| expires | Strongly Recommended | Number | How long (in days) the user ID information will be stored. 33Across recommends `90`. | `90` | +| refreshInSeconds | Strongly Recommended | Number | The interval (in seconds) for refreshing the user ID. 33Across recommends no more than 8 hours between refreshes. | `8*3600` | + +### Params + +The following settings are available in the `params` property in `userSync.userIds[]` object: + +| Param name | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| pid | Required | String | Partner ID provided by 33Across | `"0010b00002GYU4eBAH"` | 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..9e322ba449b 100644 --- a/modules/ablidaBidAdapter.js +++ b/modules/ablidaBidAdapter.js @@ -45,7 +45,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/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/adagioBidAdapter.js b/modules/adagioBidAdapter.js index a24bc889411..a76046ad2db 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -19,18 +19,18 @@ 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'; const BIDDER_CODE = 'adagio'; const LOG_PREFIX = 'Adagio:'; @@ -46,7 +46,6 @@ const MAX_SESS_DURATION = 30 * 60 * 1000; const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp'; const ADAGIO_PUBKEY_E = 65537; const CURRENCY = 'USD'; -const DEFAULT_FLOOR = 0.1; // This provide a whitelist and a basic validation // of OpenRTB 2.5 options used by the Adagio SSP. @@ -269,32 +268,13 @@ function getDevice() { }; function getSite(bidderRequest) { - let domain = ''; - let page = ''; - let referrer = ''; - 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]) { - // important note check if refererInfo.stack[0] is 'thruly' because a `null` value - // will be considered as "localhost" by the parseUrl function. - const url = parseUrl(refererInfo.stack[0]); - domain = url.hostname; - } - return { - domain, - page, - referrer + // TODO: do these fallbacks make sense? + domain: refererInfo.domain || parseDomain(refererInfo.topmostLocation) || '', + page: refererInfo.page || refererInfo.topmostLocation || '', + referrer: refererInfo.ref || getWindowSelf().document.referrer || '', + top: refererInfo.reachedTop }; }; @@ -589,13 +569,13 @@ function _getFloors(bidRequest) { const info = bidRequest.getFloor({ currency: CURRENCY, mediaType, - size: [] + size }); floors.push(cleanObj({ mt: mediaType, s: isArray(size) ? `${size[0]}x${size[1]}` : undefined, - f: (!isNaN(info.floor) && info.currency === CURRENCY) ? info.floor : DEFAULT_FLOOR + f: (!isNaN(info.floor) && info.currency === CURRENCY) ? info.floor : undefined })); } @@ -639,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) { @@ -869,7 +849,9 @@ function storeRequestInAdagioNS(bidRequest) { }], auctionId: bidRequest.auctionId, pageviewId: internal.getPageviewId(), - printNumber + printNumber, + localPbjs: '$$PREBID_GLOBAL$$', + localPbjsRef: getGlobal() }); // (legacy) Store internal adUnit information @@ -937,7 +919,45 @@ export const spec = { }); // Handle priceFloors module - bidRequest.floors = _getFloors(bidRequest); + const computedFloors = _getFloors(bidRequest); + if (isArray(computedFloors) && computedFloors.length) { + bidRequest.floors = computedFloors + + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + const bannerObj = bidRequest.mediaTypes.banner + + const computeNewSizeArray = (sizeArr = []) => { + const size = { size: sizeArr, floor: null } + const bannerFloors = bidRequest.floors.filter(floor => floor.mt === BANNER) + const BannerSizeFloor = bannerFloors.find(floor => floor.s === sizeArr.join('x')) + size.floor = (bannerFloors) ? (BannerSizeFloor) ? BannerSizeFloor.f : bannerFloors[0].f : null + return size + } + + // `bannerSizes`, internal property name + bidRequest.mediaTypes.banner.bannerSizes = (isArray(bannerObj.sizes[0])) + ? bannerObj.sizes.map(sizeArr => { + return computeNewSizeArray(sizeArr) + }) + : computeNewSizeArray(bannerObj.sizes) + } + + if (deepAccess(bidRequest, 'mediaTypes.video')) { + const videoObj = bidRequest.mediaTypes.video + const videoFloors = bidRequest.floors.filter(floor => floor.mt === VIDEO); + const playerSize = (videoObj.playerSize && isArray(videoObj.playerSize[0])) ? videoObj.playerSize[0] : videoObj.playerSize + const videoSizeFloor = (playerSize) ? videoFloors.find(floor => floor.s === playerSize.join('x')) : undefined + + bidRequest.mediaTypes.video.floor = (videoFloors) ? videoSizeFloor ? videoSizeFloor.f : videoFloors[0].f : null + } + + if (deepAccess(bidRequest, 'mediaTypes.native')) { + const nativeFloors = bidRequest.floors.filter(floor => floor.mt === NATIVE); + if (nativeFloors.length) { + bidRequest.mediaTypes.native.floor = nativeFloors[0].f + } + } + } if (deepAccess(bidRequest, 'mediaTypes.video')) { _buildVideoBidRequest(bidRequest); @@ -956,6 +976,8 @@ export const spec = { // remove useless props delete adUnitCopy.floorData; delete adUnitCopy.params.siteId; + delete adUnitCopy.userId + delete adUnitCopy.userIdAsEids groupedAdUnits[adUnitCopy.params.organizationId] = groupedAdUnits[adUnitCopy.params.organizationId] || []; groupedAdUnits[adUnitCopy.params.organizationId].push(adUnitCopy); 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..198441975d8 100644 --- a/modules/adbookpspBidAdapter.js +++ b/modules/adbookpspBidAdapter.js @@ -123,9 +123,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 f0425a174ff..0b9c10bb38a 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -1,15 +1,11 @@ // 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'; const { getConfig } = config; @@ -66,7 +62,7 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { let app, site; - const commonFpd = getConfig('ortb2') || {}; + const commonFpd = bidderRequest.ortb2 || {}; let { user } = commonFpd; if (typeof getConfig('app') === 'object') { @@ -81,7 +77,7 @@ export const spec = { } if (!site.page) { - site.page = bidderRequest.refererInfo.referer; + site.page = bidderRequest.refererInfo.page; } } @@ -206,6 +202,11 @@ export const spec = { request.is_debug = !!test; request.test = 1; } + + if (config.getConfig('coppa')) { + deepSetValue(request, 'regs.coppa', 1); + } + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); @@ -227,9 +228,6 @@ export const spec = { method: 'POST', url: 'https://' + adxDomain + '/adx/openrtb', data: JSON.stringify(request), - options: { - contentType: 'application/json' - }, bids: validBidRequests }; }, 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..b48d9d350e3 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -25,13 +25,16 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - const ADGENE_PREBID_VERSION = '1.3.0'; + 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 +48,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) { @@ -274,6 +282,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..d4aa3b035e0 100644 --- a/modules/adkernelAdnAnalyticsAdapter.js +++ b/modules/adkernelAdnAnalyticsAdapter.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..aa06b0977c5 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -7,7 +7,6 @@ import { deepSetValue, getAdUnitSizes, getDNT, - inIframe, isArray, isArrayOfNums, isEmpty, @@ -15,8 +14,7 @@ import { isPlainObject, isStr, mergeDeep, - parseGPTSingleSizeArrayToRtbSize, - parseUrl + parseGPTSingleSizeArrayToRtbSize } from '../src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -94,7 +92,8 @@ export const spec = { {code: 'ergadx'}, {code: 'turktelekom'}, {code: 'felixads'}, - {code: 'motionspots'} + {code: 'motionspots'}, + {code: 'sonic_twist'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -226,7 +225,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) => { @@ -506,7 +505,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 +534,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 1091b87a22d..a26a5e507e6 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -61,17 +61,17 @@ 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['pbadslot'] = function(b, c) { +MACRO['gpid'] = function(b, c) { const adUnit = find(auctionManager.getAdUnits(), a => b.adUnitCode === a.code); - return deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; + return deepAccess(adUnit, 'ortb2Imp.ext.gpid') || deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(b.adUnitCode).gptSlot || b.adUnitCode; }; -MACRO['pbAdSlot'] = MACRO['pbadslot']; // legacy +MACRO['pbAdSlot'] = MACRO['pbadslot'] = MACRO['gpid']; // legacy const PARAMS_DEFAULT = { 'id1': function(b) { return b.adUnitCode }, - 'id2': '%%pbadslot%%', + 'id2': '%%gpid%%', 'id3': function(b) { return b.bidder }, 'id4': function(b) { return b.adId }, 'id5': function(b) { return b.dealId }, @@ -138,7 +138,11 @@ analyticsAdapter.enableAnalytics = function(config) { toselector: config.options.toselector || function(bid) { let code = getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; // https://mathiasbynens.be/notes/css-escapes - code = code.replace(/^\d/, '\\3$& '); + try { + code = CSS.escape(code); + } catch (_) { + code = code.replace(/^\d/, '\\3$& '); + } return `#${code}` }, client: config.options.client, diff --git a/modules/adlooxAnalyticsAdapter.md b/modules/adlooxAnalyticsAdapter.md index e21261d0b8d..203b118652e 100644 --- a/modules/adlooxAnalyticsAdapter.md +++ b/modules/adlooxAnalyticsAdapter.md @@ -106,7 +106,7 @@ For example, you have a number of reporting breakdown slots available in the for tagid: 0, params: { id1: function(b) { return b.adUnitCode }, // do not change when using the Adloox RTD Provider - id2: '%%pbadslot%%', // do not change when using the Adloox RTD Provider + id2: '%%gpid%%', // do not change when using the Adloox RTD Provider id3: function(b) { return b.bidder }, id4: function(b) { return b.adId }, id5: function(b) { return b.dealId }, @@ -125,9 +125,9 @@ For example, you have a number of reporting breakdown slots available in the for The following macros are available - * `%%pbadslot%%`: [Prebid Ad Slot](https://docs.prebid.org/features/pbAdSlot.html) returns [`AdUnit.code`](https://docs.prebid.org/features/pbAdSlot.html) if set otherwise returns [`AdUnit.code`](https://docs.prebid.org/dev-docs/adunit-reference.html#adunit) + * **`%%gpid%%` (alias `%%pbadslot%%`**): [Prebid Ad Slot](https://docs.prebid.org/features/pbAdSlot.html) returns [`AdUnit.code`](https://docs.prebid.org/features/pbAdSlot.html) if set otherwise returns [`AdUnit.code`](https://docs.prebid.org/dev-docs/adunit-reference.html#adunit) * it is recommended you read the [Prebid Ad Slot section in the Adloox RTD Provider documentation](./adlooxRtdProvider.md#prebid-ad-slot) - * `%%pageurl%%`: [`canonicalUrl`](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-Page-URL) from the [`refererInfo` object](https://docs.prebid.org/dev-docs/bidder-adaptor.html#referrers) otherwise uses `referer` + * **`%%pageurl%%`**: [`canonicalUrl`](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-Page-URL) from the [`refererInfo` object](https://docs.prebid.org/dev-docs/bidder-adaptor.html#referrers) otherwise uses `referer` ### Functions diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index bb8334ec8fe..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'; @@ -216,13 +215,11 @@ function init(config, userConsent) { function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { // gptPreAuction runs *after* RTD so pbadslot may not be populated... (╯°□°)╯ ┻━┻ const adUnits = (reqBidsConfigObj.adUnits || getGlobal().adUnits).map(adUnit => { - let path = deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot'); - if (!path) path = getGptSlotInfoForAdUnitCode(adUnit.code).gptSlot; return { - path: path, + gpid: deepAccess(adUnit, 'ortb2Imp.ext.gpid') || deepAccess(adUnit, 'ortb2Imp.ext.data.pbadslot') || getGptSlotInfoForAdUnitCode(adUnit.code).gptSlot || adUnit.code, unit: adUnit }; - }).filter(adUnit => !!adUnit.path); + }).filter(adUnit => !!adUnit.gpid); let response = {}; function setSegments() { @@ -231,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; @@ -245,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) => { @@ -307,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) { @@ -315,7 +312,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { } const atfQueue = []; adUnits.map((adUnit, i) => { - const ref = [ adUnit.path ]; + const ref = [ adUnit.gpid ]; if (!config.params.slotinpath) ref.push(adUnit.unit.code); args.push(['s', ref.join('\t')]); @@ -365,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; @@ -373,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/adlooxRtdProvider.md b/modules/adlooxRtdProvider.md index 6c75fbc2d8b..466f8ed1ba2 100644 --- a/modules/adlooxRtdProvider.md +++ b/modules/adlooxRtdProvider.md @@ -81,7 +81,7 @@ To use this, you *must* also integrate the [Adloox Analytics Adapter](./adlooxAn You may optionally pass a subsection `params` in the `params` block to the Adloox RTD Provider, these will be passed through to the segment handler as is and as described by the integration guidelines. -**N.B.** If you pass `params` to the Adloox Analytics Adapter, `id1` (`AdUnit.code`) and `id2` (`%%pbadslot%%`) *must* describe a stable identifier otherwise no usable segments will be served and so they *must not* be changed; if `id1` for your inventory could contain a non-stable random number please consult with us before continuing +**N.B.** If you pass `params` to the Adloox Analytics Adapter, `id1` (`AdUnit.code`) and `id2` (`%%gpid%%`) *must* describe a stable identifier otherwise no usable segments will be served and so they *must not* be changed; if `id1` for your inventory could contain a non-stable random number please consult with us before continuing Though our segment technology is fast (less than 10ms) the time it takes for the users device to connect to our service and fetch the segments may not be. For this reason we recommend setting `auctionDelay` no lower than 100ms and if possible you should explore using user-agent sourced information such as [NetworkInformation.{rtt,downlink,...}](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation) to dynamically tune this for each user. @@ -94,7 +94,7 @@ You may use one of two ways to do achieve this: * for display inventory [using GPT](https://developers.google.com/publisher-tag/guides/get-started) you may configure Prebid.js to automatically use the [full ad unit path](https://developers.google.com/publisher-tag/reference#googletag.Slot_getAdUnitPath) 1. include the [`gptPreAuction` module](https://docs.prebid.org/dev-docs/modules/gpt-pre-auction.html) 1. wrap both `pbjs.setConfig({...})` and `pbjs.enableAnalytics({...})` with `googletag.cmd.push(function() { ... })` - * set `pbadslot` in the [first party data](https://docs.prebid.org/dev-docs/adunit-reference.html#first-party-data) variable `AdUnit.ortb2Imp.ext.data.pbadslot` for all your ad units + * set `gpid` (or `pbadslot`) in the [first party data](https://docs.prebid.org/dev-docs/adunit-reference.html#first-party-data) variable `AdUnit.ortb2Imp.ext.gpid` (or `AdUnit.ortb2Imp.ext.data.pbadslot`) for all your ad units ## Timeouts diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index 666e9aea309..4d47c4699ab 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -5,7 +5,7 @@ import {config} from '../src/config.js'; const BIDDER_CODE = 'adman'; const AD_URL = 'https://pub.admanmedia.com/?c=o&m=multi'; -const URL_SYNC = 'https://pub.admanmedia.com'; +const URL_SYNC = 'https://sync.admanmedia.com'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -65,8 +65,9 @@ export const spec = { buildRequests: (validBidRequests = [], bidderRequest) => { 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; @@ -110,6 +111,7 @@ export const spec = { if (bid.userId) { getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); getUserId(placement.eids, bid.userId.lotamePanoramaId, 'lotame.com'); + getUserId(placement.eids, bid.userId.idx, 'idx.lat'); } if (traff === VIDEO) { placement.playerSize = bid.mediaTypes[VIDEO].playerSize; diff --git a/modules/admaruBidAdapter.js b/modules/admaruBidAdapter.js new file mode 100644 index 00000000000..65f62c77e26 --- /dev/null +++ b/modules/admaruBidAdapter.js @@ -0,0 +1,81 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const ADMARU_ENDPOINT = 'https://p1.admaru.net/AdCall'; +const BIDDER_CODE = 'admaru'; + +const DEFAULT_BID_TTL = 360; + +function parseBid(rawBid, currency) { + const bid = {}; + + bid.cpm = rawBid.price; + bid.impid = rawBid.impid; + bid.requestId = rawBid.impid; + bid.netRevenue = true; + bid.dealId = ''; + bid.creativeId = rawBid.crid; + bid.currency = currency; + bid.ad = rawBid.adm; + bid.width = rawBid.w; + bid.height = rawBid.h; + bid.mediaType = BANNER; + bid.ttl = DEFAULT_BID_TTL; + + return bid; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!(bid && bid.params && bid.params.pub_id && bid.params.adspace_id); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + const payload = { + pub_id: bid.params.pub_id, + adspace_id: bid.params.adspace_id, + bidderRequestId: bid.bidderRequestId, + bidId: bid.bidId + }; + + return { + method: 'GET', + url: ADMARU_ENDPOINT, + data: payload, + } + }) + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + let bid = null; + + if (!serverResponse.hasOwnProperty('body') || !serverResponse.body.hasOwnProperty('seatbid')) { + return bidResponses; + } + + const serverBody = serverResponse.body; + const seatbid = serverBody.seatbid; + + for (let i = 0; i < seatbid.length; i++) { + if (!seatbid[i].hasOwnProperty('bid')) { + continue; + } + + const innerBids = seatbid[i].bid; + for (let j = 0; j < innerBids.length; j++) { + bid = parseBid(innerBids[j], serverBody.cur); + + bidResponses.push(bid); + } + } + + return bidResponses; + } +} + +registerBidder(spec); diff --git a/modules/admaruBidAdapter.md b/modules/admaruBidAdapter.md new file mode 100644 index 00000000000..9985a660ac6 --- /dev/null +++ b/modules/admaruBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Admaru Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@admaru.com +``` + +# Description + +Module that connects to Admaru demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "admaru", + params: { + pub_id: '1234', // string - required + adspace_id: '1234' // string - required + } + } + ] + } + ]; +``` 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..b2f9c09aa84 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -32,15 +32,16 @@ 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 = { diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 9e05ea664d8..b9499af7d9d 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,21 +68,26 @@ 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 }); } @@ -89,9 +95,11 @@ export const spec = { 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 +114,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 +130,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 0c23f5e3d8a..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 + '=' + encodeURIComponent(v)); + payload.push(k + '=' + (URL_SAFE_FIELDS[k] ? v : encodeURIComponent(v))); }); const randomizedPart = Math.random().toString().slice(2); @@ -17,15 +21,20 @@ function buildRequest(masterBidRequests, masterId, gdprConsent) { let emiter; const payload = { id: masterId, - aosspsizes: [] + aosspsizes: [], + slaves: [] }; if (gdprConsent) { payload.gdpr_consent = gdprConsent.consentString || undefined; payload.gdpr = gdprConsent.gdprApplies ? 1 : 0; } + const anyKey = Object.keys(masterBidRequests)[0]; + if (masterBidRequests[anyKey].schain) { + payload.schain = serializeSupplyChain(masterBidRequests[anyKey].schain); + } const bidIdMap = {}; - + const uniquePartLength = 10; _each(masterBidRequests, function(bid, slaveId) { if (!emiter) { emiter = bid.params.emiter; @@ -34,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', @@ -48,6 +59,30 @@ function buildRequest(masterBidRequests, masterId, gdprConsent) { }; } +const SCHAIN_FIELDS = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext']; +function serializeSupplyChain(schain) { + const header = `${schain.ver},${schain.complete}!`; + + const serializedNodes = []; + _each(schain.nodes, function(node) { + const serializedNode = SCHAIN_FIELDS + .map(fieldName => { + if (fieldName === 'ext') { + // do not serialize ext data, just mark if it was available + return ('ext' in node ? '1' : '0'); + } + if (fieldName in node) { + return encodeURIComponent(node[fieldName]).replace(/!/g, '%21'); + } + return ''; + }) + .join(','); + serializedNodes.push(serializedNode); + }); + + return header + serializedNodes.join('!'); +} + function assignToMaster(bidRequest, bidRequestsByMaster) { const masterId = bidRequest.params.masterId; const slaveId = bidRequest.params.slaveId; diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js index 44c0c6868cf..40809c59093 100644 --- a/modules/adomikAnalyticsAdapter.js +++ b/modules/adomikAnalyticsAdapter.js @@ -4,7 +4,7 @@ import adapterManager from '../src/adapterManager.js'; import {logInfo} from '../src/utils.js'; import {find, findIndex} from '../src/polyfill.js'; -// Events used in adomik analytics adapter +// Events used in adomik analytics adapter. const auctionInit = CONSTANTS.EVENTS.AUCTION_INIT; const auctionEnd = CONSTANTS.EVENTS.AUCTION_END; const bidRequested = CONSTANTS.EVENTS.BID_REQUESTED; @@ -30,17 +30,13 @@ let adomikAdapter = Object.assign(adapter({}), break; case bidResponse: - adomikAdapter.bucketEvents.push({ - type: 'response', - event: adomikAdapter.buildBidResponse(args) - }); + adomikAdapter.saveBidResponse(args); break; case bidWon: - adomikAdapter.sendWonEvent({ - id: args.adId, - placementCode: args.adUnitCode - }); + args.id = args.adId; + args.placementCode = args.adUnitCode; + adomikAdapter.sendWonEvent(args); break; case bidRequested: @@ -69,16 +65,28 @@ adomikAdapter.initializeBucketEvents = function() { adomikAdapter.bucketEvents = []; } +adomikAdapter.saveBidResponse = function(args) { + let responseSaved = adomikAdapter.bucketEvents.find((bucketEvent) => + bucketEvent.type == 'response' && bucketEvent.event.id == args.id + ); + if (responseSaved) { return true; } + adomikAdapter.bucketEvents.push({ + type: 'response', + event: adomikAdapter.buildBidResponse(args) + }); +} + adomikAdapter.maxPartLength = function () { return (ua.includes(' MSIE ')) ? 1600 : 60000; }; adomikAdapter.sendTypedEvent = function() { + let [testId, testValue] = adomikAdapter.getKeyValues(); const groupedTypedEvents = adomikAdapter.buildTypedEvents(); const bulkEvents = { - testId: adomikAdapter.currentContext.testId, - testValue: adomikAdapter.currentContext.testValue, + testId: testId, + testValue: testValue, uid: adomikAdapter.currentContext.uid, ahbaid: adomikAdapter.currentContext.id, hostname: window.location.hostname, @@ -114,10 +122,8 @@ adomikAdapter.sendTypedEvent = function() { const stringBulkEvents = JSON.stringify(bulkEvents) logInfo('Events sent to adomik prebid analytic ' + stringBulkEvents); - // Encode object in base64 const encodedBuf = window.btoa(stringBulkEvents); - // Create final url and split it (+endpoint length) const encodedUri = encodeURIComponent(encodedBuf); const maxLength = adomikAdapter.maxPartLength(); const splittedUrl = encodedUri.match(new RegExp(`.{1,${maxLength}}`, 'g')); @@ -130,16 +136,18 @@ adomikAdapter.sendTypedEvent = function() { }; adomikAdapter.sendWonEvent = function (wonEvent) { - let keyValues = { testId: adomikAdapter.currentContext.testId, testValue: adomikAdapter.currentContext.testValue } - wonEvent = {...wonEvent, ...keyValues} - const stringWonEvent = JSON.stringify(wonEvent) + let [testId, testValue] = adomikAdapter.getKeyValues(); + let keyValues = { testId: testId, testValue: testValue }; + let samplingInfo = { sampling: adomikAdapter.currentContext.sampling }; + wonEvent = { ...adomikAdapter.buildBidResponse(wonEvent), ...keyValues, ...samplingInfo }; + + const stringWonEvent = JSON.stringify(wonEvent); logInfo('Won event sent to adomik prebid analytic ' + stringWonEvent); - // Encode object in base64 const encodedBuf = window.btoa(stringWonEvent); const encodedUri = encodeURIComponent(encodedBuf); const img = new Image(1, 1); - img.src = `https://${adomikAdapter.currentContext.url}/?q=${encodedUri}&id=${adomikAdapter.currentContext.id}&won=true` + img.src = `https://${adomikAdapter.currentContext.url}/?q=${encodedUri}&id=${adomikAdapter.currentContext.id}&won=true`; } adomikAdapter.buildBidResponse = function (bid) { @@ -201,33 +209,49 @@ adomikAdapter.buildTypedEvents = function () { return groupedTypedEvents; } -adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics; +adomikAdapter.getKeyValues = function () { + let preventTest = sessionStorage.getItem(window.location.hostname + '_NoAdomikTest') + let inScope = sessionStorage.getItem(window.location.hostname + '_AdomikTestInScope') + let keyValues = JSON.parse(sessionStorage.getItem(window.location.hostname + '_AdomikTest')) + let testId; + let testValue; + if (typeof (keyValues) === 'object' && keyValues != undefined && !preventTest && inScope) { + testId = keyValues.testId + testValue = keyValues.testOptionLabel + } + return [testId, testValue] +} -adomikAdapter.enableAnalytics = function (config) { - adomikAdapter.currentContext = {}; - const initOptions = config.options; - - _sampled = typeof config === 'undefined' || - typeof config.sampling === 'undefined' || - Math.random() < parseFloat(config.sampling); - - if (_sampled) { - if (initOptions) { - adomikAdapter.currentContext = { - uid: initOptions.id, - url: initOptions.url, - testId: initOptions.testId, - testValue: initOptions.testValue, - id: '', - timeouted: false, - sampling: config.sampling - } - logInfo('Adomik Analytics enabled with config', initOptions); - adomikAdapter.adapterEnableAnalytics(config); - } - } else { - logInfo('Adomik Analytics ignored for sampling', config.sampling); +adomikAdapter.enable = function(options) { + adomikAdapter.currentContext = { + uid: options.id, + url: options.url, + id: '', + timeouted: false, + sampling: options.sampling } + logInfo('Adomik Analytics enabled with config', options); + adomikAdapter.adapterEnableAnalytics(options); +}; + +adomikAdapter.checkOptions = function(options) { + if (typeof options !== 'undefined') { + if (options.id && options.url) { adomikAdapter.enable(options); } else { logInfo('Adomik Analytics disabled because id and/or url is missing from config', options); } + } else { logInfo('Adomik Analytics disabled because config is missing'); } +}; + +adomikAdapter.checkSampling = function(options) { + _sampled = typeof options === 'undefined' || + typeof options.sampling === 'undefined' || + (options.sampling > 0 && Math.random() < parseFloat(options.sampling)); + if (_sampled) { adomikAdapter.checkOptions(options) } else { logInfo('Adomik Analytics ignored for sampling', options.sampling); } +}; + +adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics; + +adomikAdapter.enableAnalytics = function ({ provider, options }) { + logInfo('Adomik Analytics enableAnalytics', provider); + adomikAdapter.checkSampling(options); }; adapterManager.registerAnalyticsAdapter({ diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index a28ab4257df..af9cb8b5008 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -4,14 +4,13 @@ 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'; 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', 'minduration', 'maxduration', 'protocols']; -const DOMAIN_REGEX = new RegExp('//([^/]*)'); +const REQUIRED_VIDEO_PARAMS = ['mimes', 'protocols']; const FIRST_PRICE = 1; const IMP_BUILDER = { banner: buildBanner, video: buildVideo, native: buildNative }; const NATIVE_PLACEMENTS = { @@ -43,19 +42,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 +49,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 +86,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, + }, + }; } /** 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/adprimeBidAdapter.js b/modules/adprimeBidAdapter.js index d64874c393e..4b53c8441ef 100644 --- a/modules/adprimeBidAdapter.js +++ b/modules/adprimeBidAdapter.js @@ -52,8 +52,9 @@ export const spec = { buildRequests: (validBidRequests = [], bidderRequest) => { 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..e449adce1a1 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -178,11 +178,15 @@ export const spec = { }; function buildRequest(validBidRequests, bidderRequest) { - let qid = Math.random().toString(36).substring(2) + Date.now().toString(36); + let gnerateQid = (Math.random().toString(36).substring(2)) + ((new Date().getTime()).toString(36)); let bid = validBidRequests; + let qid = gnerateQid; if (storage.getDataFromLocalStorage('qid')) { qid = storage.getDataFromLocalStorage('qid'); + if (qid && qid.includes('%7B%22')) { + qid = gnerateQid; + } } else { storage.setDataInLocalStorage('qid', qid); } diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 3d4de7c7b9d..3a68513cde2 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -127,7 +127,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 +136,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); @@ -445,6 +445,13 @@ function bidToTag(bid) { 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..e7cac348b3e 100644 --- a/modules/adrinoBidAdapter.js +++ b/modules/adrinoBidAdapter.js @@ -1,6 +1,7 @@ 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'; const BIDDER_CODE = 'adrino'; const REQUEST_METHOD = 'POST'; @@ -12,6 +13,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) && @@ -26,11 +31,14 @@ export const spec = { 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, + // TODO: is 'page' the right value here? + referer: bidderRequest.refererInfo.page, userAgent: navigator.userAgent, } @@ -43,7 +51,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 +74,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/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..c4f2bc65655 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; diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index f309ed4e96e..6c6fca13d7b 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -20,6 +20,8 @@ const HOST_GETTERS = { onefiftytwomedia: () => 'ghb.ads.152media.com', bidsxchange: () => 'ghb.hbd.bidsxchange.com', streamkey: () => 'ghb.hb.streamkey.net', + janet: () => 'ghb.bidder.jmgads.com', + pgam: () => 'ghb.pgamssp.com', } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -35,8 +37,10 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, gvlid: 410, - aliases: ['onefiftytwomedia', 'selectmedia', 'appaloosa', 'bidsxchange', 'streamkey', - { code: 'navelix', gvlid: 380 } + aliases: ['onefiftytwomedia', 'appaloosa', 'bidsxchange', 'streamkey', 'janet', + { code: 'selectmedia', gvlid: 775 }, + { code: 'navelix', gvlid: 380 }, + 'pgam' ], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { @@ -157,7 +161,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; diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index 283e1273150..b8cdb1f99aa 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -133,8 +133,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 }; } diff --git a/modules/aduptechBidAdapter.js b/modules/aduptechBidAdapter.js index 1186e0410ab..1bb88c11cde 100644 --- a/modules/aduptechBidAdapter.js +++ b/modules/aduptechBidAdapter.js @@ -1,7 +1,6 @@ -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'; export const BIDDER_CODE = 'aduptech'; export const ENDPOINT_URL_PUBLISHER_PLACEHOLDER = '{PUBLISHER}'; @@ -37,19 +36,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 +47,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 +94,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 * @@ -248,6 +253,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/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 81872100cd1..9cfbc91b48b 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -2,19 +2,19 @@ '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'; @@ -67,7 +67,7 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { let app, site; - const commonFpd = getConfig('ortb2') || {}; + const commonFpd = bidderRequest.ortb2 || {}; let { user } = commonFpd; if (typeof getConfig('app') === 'object') { @@ -82,8 +82,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..4db01024208 100644 --- a/modules/adxpremiumAnalyticsAdapter.js +++ b/modules/adxpremiumAnalyticsAdapter.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)); diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 1a0074e668b..5b3e885f2ed 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -1,6 +1,5 @@ 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'; @@ -77,6 +76,9 @@ export const spec = { if (typeof bidReq.getFloor === 'function') { accumulator[bidReq.bidId].Pricing = getFloor(bidReq, size, mediatype); } + if (bidReq.schain) { + accumulator[bidReq.bidId].SChain = bidReq.schain; + } if (mediatype === NATIVE) { let nativeReq = bidReq.mediaTypes.native; if (nativeReq.type === 'image') { @@ -165,23 +167,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,20 +221,21 @@ function createEndpointQS(bidderRequest) { if (bidderRequest) { const ref = bidderRequest.refererInfo; - if (ref) { - qs.RefererUrl = encodeURIComponent(ref.referer); + if (ref?.location) { + // TODO: is 'location' the right value here? + qs.RefererUrl = encodeURIComponent(ref.location); if (ref.numIframes > 0) { qs.SafeFrame = true; } } } - const can = getCanonicalUrl(); + const can = bidderRequest?.refererInfo?.canonicalUrl; if (can) { qs.CanonicalUrl = encodeURIComponent(can); } - const domain = config.getConfig('publisherDomain'); + const domain = bidderRequest?.refererInfo?.domain; if (domain) { qs.PublisherDomain = encodeURIComponent(domain); } @@ -350,14 +336,6 @@ function getTrackers(eventsArray, jsTrackers) { return result; } -function getVideoAd(response) { - var adJson = {}; - if (typeof response.Ad === 'string' && response.Ad.indexOf('\/\*PREBID\*\/') > 0) { - adJson = JSON.parse(response.Ad.match(/\/\*PREBID\*\/(.*)\/\*PREBID\*\//)[1]); - return deepAccess(adJson, 'Content.MainVideo.Vast'); - } -} - function getNativeAssets(response, nativeConfig) { if (typeof response.Native === 'object') { return response.Native; @@ -486,8 +464,10 @@ function createBid(response, bidRequests) { }; // retreive video response if present - const vast64 = response.Vast || getVideoAd(response); + const vast64 = response.Vast; if (vast64) { + bid.width = response.Width; + bid.height = response.Height; bid.vastXml = window.atob(vast64); bid.mediaType = 'video'; } else if (request.Native) { 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 b2e78a7df78..3578cc4b87e 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -5,18 +5,25 @@ * @module modules/airgridRtdProvider * @requires module:modules/realTimeData */ -import {config} from '../src/config.js'; -import {submodule} from '../src/hook.js'; -import {mergeDeep, isPlainObject, deepSetValue, deepAccess} from '../src/utils.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import {getStorageManager} from '../src/storageManager.js'; +import { config } from '../src/config.js'; +import { submodule } from '../src/hook.js'; +import { + mergeDeep, + deepSetValue, + deepAccess, +} from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { getStorageManager } from '../src/storageManager.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'airgrid'; const AG_TCF_ID = 782; -export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids' +export const AG_AUDIENCE_IDS_KEY = 'edkt_matched_audience_ids'; -export const storage = getStorageManager({gvlid: AG_TCF_ID, moduleName: SUBMODULE_NAME}); +export const storage = getStorageManager({ + gvlid: AG_TCF_ID, + moduleName: SUBMODULE_NAME, +}); /** * Attach script tag to DOM @@ -24,13 +31,13 @@ export const storage = getStorageManager({gvlid: AG_TCF_ID, moduleName: SUBMODUL * @return {void} */ export function attachScriptTagToDOM(rtdConfig) { - var edktInitializor = window.edktInitializor = window.edktInitializor || {}; + var edktInitializor = (window.edktInitializor = window.edktInitializor || {}); if (!edktInitializor.invoked) { edktInitializor.invoked = true; edktInitializor.accountId = rtdConfig.params.accountId; edktInitializor.publisherId = rtdConfig.params.publisherId; edktInitializor.apiKey = rtdConfig.params.apiKey; - edktInitializor.load = function(e) { + edktInitializor.load = function (e) { var p = e || 'sdk'; var n = document.createElement('script'); n.type = 'module'; @@ -48,7 +55,7 @@ export function attachScriptTagToDOM(rtdConfig) { */ export function getMatchedAudiencesFromStorage() { const audiences = storage.getDataFromLocalStorage(AG_AUDIENCE_IDS_KEY); - if (!audiences) return [] + if (!audiences) return []; try { return JSON.parse(audiences); } catch (e) { @@ -68,32 +75,30 @@ function setAudiencesToAppNexusAdUnits(adUnits, audiences) { if (bid.bidder && bid.bidder === 'appnexus') { deepSetValue(bid, 'params.keywords.perid', 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(); + 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) { + config.setConfig({ + appnexusAuctionKeywords: { + perid: audiences, + }, }); } @@ -116,23 +121,29 @@ function init(rtdConfig, userConsent) { * @param {Object} userConsent * @return {void} */ -export function passAudiencesToBidders(bidConfig, onDone, rtdConfig, userConsent) { +export function passAudiencesToBidders( + bidConfig, + onDone, + rtdConfig, + userConsent +) { const adUnits = bidConfig.adUnits || getGlobal().adUnits; const audiences = getMatchedAudiencesFromStorage(); if (audiences.length > 0) { - setAudiencesUsingBidderOrtb2(rtdConfig, audiences); + setAudiencesUsingAppNexusAuctionKeywords(audiences); + mergeDeep(bidConfig?.ortb2Fragments?.bidder, getAudiencesAsBidderOrtb2(rtdConfig, audiences)); if (adUnits) { setAudiencesToAppNexusAdUnits(adUnits, audiences); } } onDone(); -}; +} /** @type {RtdSubmodule} */ export const airgridSubmodule = { name: SUBMODULE_NAME, init: init, - getBidRequestData: passAudiencesToBidders + getBidRequestData: passAudiencesToBidders, }; submodule(MODULE_NAME, airgridSubmodule); diff --git a/modules/airgridRtdProvider.md b/modules/airgridRtdProvider.md index 7ee502b4c10..6251c63fce9 100644 --- a/modules/airgridRtdProvider.md +++ b/modules/airgridRtdProvider.md @@ -1,15 +1,17 @@ - --- - layout: page_v2 - title: AirGrid RTD SubModule - description: Client-side, cookieless and privacy-first audiences. - page_type: module - module_type: rtd - module_code : example - enable_download : true - sidebarType : 1 - --- - -# AirGrid +--- +layout: page_v2 +title: AirGrid RTD Provider +display_name: AirGrid RTD Provider +description: Client-side, cookieless and privacy-first audiences. +page_type: module +module_type: rtd +module_code : airgridRtdProvider +enable_download : true +vendor_specific: true +sidebarType : 1 +--- + +# AirGrid RTD Provider AirGrid is a privacy-first, cookie-less audience platform. Designed to help publishers increase inventory yield, whilst providing audience signal to buyers in the bid request, without exposing raw user level data to any party. @@ -17,13 +19,17 @@ whilst providing audience signal to buyers in the bid request, without exposing This real-time data module provides quality first-party data, contextual data, site-level data and more that is injected into bid request objects destined for different bidders in order to optimize targeting. +{:.no_toc} +* TOC +{:toc} + ## Usage -Compile the Halo RTD module into your Prebid build: +Compile the AirGrid RTD module (`airgridRtdProvider`) into your Prebid build, along with the parent RTD Module (`rtdModule`): `gulp build --modules=rtdModule,airgridRtdProvider,appnexusBidAdapter` -Add the AirGrid RTD provider to your Prebid config. In this example we will configure publisher 1234 to retrieve segments from Audigent. See the "Parameter Descriptions" below for more detailed information of the configuration parameters. +Next we configure the module, via `pbjs.setConfig`. See the **Parameter Descriptions** below for more detailed information of the configuration parameters. ```js pbjs.setConfig( @@ -50,6 +56,7 @@ pbjs.setConfig( ### Parameter Descriptions +{: .table .table-bordered .table-striped } | Name |Type | Description | Notes | | :------------ | :------------ | :------------ |:------------ | | name | `String` | RTD sub module name | Always 'airgrid' | @@ -61,7 +68,7 @@ pbjs.setConfig( _Note: Although the module supports passing segment data to any bidder using the ORTB2 spec, there is no way for this to be currently monetised. Please reach out to support, to discuss using bidders other than Xandr/AppNexus._ -If you do not have your own `apiKey`, `accountId` & `publisherId` please reach out to [support@airgrid.io](mailto:support@airgrid.io) +If you do not have your own `apiKey`, `accountId` & `publisherId` please reach out to [support@airgrid.io](mailto:support@airgrid.io) or you can sign up via the [AirGrid platform](https://app.airgrid.io). ## Testing @@ -89,7 +96,7 @@ If you require further assistance or are interested in discussing the module fun - [hello@airgrid.io](mailto:hello@airgrid.io) for general questions. - [support@airgrid.io](mailto:support@airgrid.io) for technical questions. -You are also able to find more examples and other integration routes on the [AirGrid docs site](docs.airgrid.io). +You are also able to find more examples and other integration routes on the [AirGrid docs site](https://docs.airgrid.io), or learn more on our [site](https://airgrid.io)! Happy Coding! 😊 The AirGrid Team. diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index a9364a7a05f..5991439740f 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -36,7 +36,7 @@ export const spec = { */ buildRequests: function(validBidRequests, bidderRequest) { 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 aca984d39c8..f167cb7f3ea 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -6,16 +6,25 @@ * @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'; +import { loadExternalScript } from '../src/adloader.js'; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'dap'; +const MODULE_CODE = 'akamaidap'; + +export const DAP_TOKEN = 'async_dap_token'; +export const DAP_MEMBERSHIP = 'async_dap_membership'; +export const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership'; +export const DAP_SS_ID = 'dap_ss_id'; +export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds +export const DAP_MAX_RETRY_TOKENIZE = 1; +export const DAP_CLIENT_ENTROPY = 'dap_client_entropy' -export const SEGMENTS_STORAGE_KEY = 'akamaiDapSegments'; export const storage = getStorageManager({gvlid: null, moduleName: SUBMODULE_NAME}); +let dapRetryTokenize = 0; /** * Lazy merge objects. @@ -34,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'); } @@ -53,61 +61,68 @@ export function addRealTimeData(rtd) { * Real-time data retrieval from Audigent * @param {Object} reqBidsConfigObj * @param {function} onDone - * @param {Object} rtdConfi + * @param {Object} rtdConfig * @param {Object} userConsent */ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { - logInfo('DEBUG(getRealTimeData) - ENTER'); + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); + let loadScriptPromise = new Promise((resolve, reject) => { + if (rtdConfig && rtdConfig.params && rtdConfig.params.dapEntropyTimeout && Number.isInteger(rtdConfig.params.dapEntropyTimeout)) { + setTimeout(reject, rtdConfig.params.dapEntropyTimeout, Error('DapEntropy script could not be loaded')); + } + if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) { + logMessage('Using cached entropy'); + resolve(); + } else { + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); + } else { + if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { + loadExternalScript(rtdConfig.params.dapEntropyUrl, MODULE_CODE, () => { dapUtils.dapGetEntropy(resolve, reject) }); + } else { + reject(Error('Please check if dapEntropyUrl is specified and is valid under config.params')); + } + } + } + }); + loadScriptPromise + .catch((error) => { + logError('Entropy could not be calculated due to: ', error.message); + }) + .finally(() => { + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + }); +} + +export function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + logInfo('DEBUG(generateRealTimeData) - ENTER'); logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname); logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion); - let jsonData = storage.getDataFromLocalStorage(SEGMENTS_STORAGE_KEY); - if (jsonData) { - let data = JSON.parse(jsonData); - if (data.rtd) { - addRealTimeData(data.rtd); - onDone(); - logInfo('DEBUG(getRealTimeData) - 1'); - // Don't return - ensure the data is always fresh. + dapRetryTokenize = 0; + var jsonData = null; + if (rtdConfig && isPlainObject(rtdConfig.params)) { + if (rtdConfig.params.segtax == 504) { + let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage(); + if (encMembership) { + jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax) + } + } else { + let membership = dapUtils.dapGetMembershipFromLocalStorage(); + if (membership) { + jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax) + } } } - - if (rtdConfig && isPlainObject(rtdConfig.params)) { - let config = { - api_hostname: rtdConfig.params.apiHostname, - api_version: rtdConfig.params.apiVersion, - domain: rtdConfig.params.domain, - segtax: rtdConfig.params.segtax - }; - let identity = { - type: rtdConfig.params.identityType - }; - let token = dapUtils.dapGetToken(config, identity, rtdConfig.params.tokenTtl); - if (token !== null) { - let membership = dapUtils.dapGetMembership(config, token); - let udSegment = dapUtils.dapMembershipToRtbSegment(membership, config); - logMessage('DEBUG(getRealTimeData) - token: ' + token + ', user.data.segment: ', udSegment); - let data = { - rtd: { - ortb2: { - user: { - data: [ - udSegment - ] - }, - site: { - ext: { - data: { - dapSAID: membership.said - } - } - } - } - } - }; - storage.setDataInLocalStorage(SEGMENTS_STORAGE_KEY, JSON.stringify(data)); + if (jsonData) { + if (jsonData.rtd) { + addRealTimeData(bidConfig.ortb2Fragments?.global, jsonData.rtd); onDone(); + logInfo('DEBUG(generateRealTimeData) - 1'); + // Don't return - ensure the data is always fresh. } } + // Calling setTimeout to release the main thread so that the bid request could be sent. + setTimeout(dapUtils.callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); } /** @@ -117,6 +132,9 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { * @return {boolean} */ function init(provider, userConsent) { + if (dapUtils.checkConsent(userConsent) === false) { + return false; + } return true; } @@ -128,104 +146,216 @@ export const akamaiDapRtdSubmodule = { }; submodule(MODULE_NAME, akamaiDapRtdSubmodule); - export const dapUtils = { - dapGetToken: function(config, identity, ttl) { + callDapAPIs: function(bidConfig, onDone, rtdConfig, userConsent) { + if (rtdConfig && isPlainObject(rtdConfig.params)) { + let config = { + api_hostname: rtdConfig.params.apiHostname, + api_version: rtdConfig.params.apiVersion, + domain: rtdConfig.params.domain, + segtax: rtdConfig.params.segtax, + identity: {type: rtdConfig.params.identityType} + }; + 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(ortb2, config, token, onDone) // Get the encrypted membership from server + refreshMembership = false; + } else { + dapUtils.dapRefreshMembership(ortb2, config, token, onDone) // Get the membership from server + refreshMembership = false; + } + } + dapUtils.dapRefreshToken(ortb2, config, refreshMembership, onDone) // Refresh Token and membership in all the cases + } + }, + dapGetEntropy: function(resolve, reject) { + if (typeof window.dapCalculateEntropy === 'function') { + window.dapCalculateEntropy(resolve, reject); + } else { + reject(Error('window.dapCalculateEntropy function is not defined')) + } + }, + + dapGetTokenFromLocalStorage: function(ttl) { let now = Math.round(Date.now() / 1000.0); // in seconds - let storageName = 'async_dap_token'; let token = null; - - if (ttl == 0) { - localStorage.removeItem(storageName); + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)); + if (item) { + if (now < item.expires_at) { + token = item.token; + } } + return token; + }, - let item = JSON.parse(localStorage.getItem(storageName)); - if (item == null) { - item = { - expires_at: now - 1, - token: null - }; - } else { - token = item.token; - } - - if (now > item.expires_at) { - dapUtils.dapLog('Token missing or expired, fetching a new one...'); - // Trigger a refresh - let configAsync = {...config}; - dapUtils.dapTokenize(configAsync, identity, - function(token, status, xhr) { - item.expires_at = now + ttl; - item.token = token; - localStorage.setItem(storageName, JSON.stringify(item)); - dapUtils.dapLog('Successfully updated and stored token; expires in ' + ttl + ' seconds'); - let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); - if (deviceId100 != null) { - localStorage.setItem('dap_deviceId100', deviceId100); - dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + 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 + let item = {} + let configAsync = {...config}; + dapUtils.dapTokenize(configAsync, config.identity, onDone, + function(token, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(token) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } + item.token = token; + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored token; expires at ' + item.expires_at); + let dapSSID = xhr.getResponseHeader('Akamai-DAP-SS-ID'); + if (dapSSID) { + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify(dapSSID)); + } + let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); + if (deviceId100 != null) { + storage.setDataInLocalStorage('dap_deviceId100', deviceId100); + dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); + } + if (refreshMembership) { + if (config.segtax == 504) { + dapUtils.dapRefreshEncryptedMembership(ortb2, config, token, onDone); + } else { + dapUtils.dapRefreshMembership(ortb2, config, token, onDone); } - }, - function(xhr, status, error) { - logError('ERROR(' + error + '): failed to retrieve token! ' + status); } - ); - } - - return token; + }, + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve token! ' + status); + onDone() + } + ); }, - dapGetMembership: function(config, token) { + dapGetMembershipFromLocalStorage: function() { let now = Math.round(Date.now() / 1000.0); // in seconds - let storageName = 'async_dap_membership'; - let maxTtl = 3600; // if the cached membership is older than this, return null let membership = null; - let item = JSON.parse(localStorage.getItem(storageName)); - if (item == null || (now - item.expires_at) > maxTtl) { - item = { - expires_at: now - 1, - said: null, - cohorts: null, - attributes: null - }; - } else { - membership = { - said: item.said, - cohorts: item.cohorts, - attributes: null - }; + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)); + if (item) { + if (now < item.expires_at) { + membership = { + said: item.said, + cohorts: item.cohorts, + attributes: null + }; + } } + return membership; + }, - // Always refresh the cached membership. + dapRefreshMembership: function(ortb2, config, token, onDone) { + let now = Math.round(Date.now() / 1000.0); // in seconds + let item = {} let configAsync = {...config}; - dapUtils.dapMembership(configAsync, token, - function(membership, status, xhr) { - item.expires_at = now + maxTtl; + dapUtils.dapMembership(configAsync, token, onDone, + function(membership, status, xhr, onDone) { + item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; + let exp = dapUtils.dapExtractExpiryFromToken(membership.said) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } item.said = membership.said; item.cohorts = membership.cohorts; - localStorage.setItem(storageName, JSON.stringify(item)); + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(item)); dapUtils.dapLog('Successfully updated and stored membership:'); dapUtils.dapLog(item); + + let data = dapUtils.dapGetRtdObj(item, config.segtax) + dapUtils.checkAndAddRealtimeData(ortb2, data, config.segtax); + onDone(); }, - function(xhr, status, error) { + function(xhr, status, error, onDone) { logError('ERROR(' + error + '): failed to retrieve membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { + dapRetryTokenize++; + dapUtils.dapRefreshToken(ortb2, config, true, onDone); + } else { + onDone(); + } } ); + }, - return membership; + dapGetEncryptedMembershipFromLocalStorage: function() { + let now = Math.round(Date.now() / 1000.0); // in seconds + let encMembership = null; + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)); + if (item) { + if (now < item.expires_at) { + encMembership = { + encryptedSegments: item.encryptedSegments + }; + } + } + return encMembership; + }, + + 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) + if (typeof exp == 'number') { + item.expires_at = exp - 10; + } + item.encryptedSegments = encToken; + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored encrypted membership:'); + dapUtils.dapLog(item); + + let encData = dapUtils.dapGetEncryptedRtdObj(item, 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(ortb2, config, true, onDone); + } else { + onDone(); + } + } + ); + }, + + /** + * DESCRIPTION + * Extract expiry value from a token + */ + dapExtractExpiryFromToken: function(token) { + let exp = null; + if (token) { + const tokenArray = token.split('..'); + if (tokenArray && tokenArray.length > 0) { + let decode = atob(tokenArray[0]) + let header = JSON.parse(decode.replace(/"/g, '"')); + exp = header.exp; + } + } + return exp }, /** * DESCRIPTION * * Convert a DAP membership response to an OpenRTB2 segment object suitable - * for insertion into user.data.segment or site.data.segment. + * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. */ - dapMembershipToRtbSegment: function(membership, config) { + dapGetRtdObj: function(membership, segtax) { let segment = { name: 'dap.akamai.com', ext: { - 'segtax': config.segtax + 'segtax': segtax }, segment: [] }; @@ -234,7 +364,82 @@ export const dapUtils = { segment.segment.push({ id: i }); } } - return segment; + let data = { + rtd: { + ortb2: { + user: { + data: [ + segment + ] + }, + site: { + ext: { + data: { + dapSAID: membership.said + } + } + } + } + } + }; + return data; + }, + + /** + * DESCRIPTION + * + * Convert a DAP membership response to an OpenRTB2 segment object suitable + * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. + */ + dapGetEncryptedRtdObj: function(encToken, segtax) { + let segment = { + name: 'dap.akamai.com', + ext: { + 'segtax': segtax + }, + segment: [] + }; + if (encToken != null) { + segment.segment.push({ id: encToken.encryptedSegments }); + } + let encData = { + rtd: { + ortb2: { + user: { + data: [ + segment + ] + } + } + } + }; + return encData; + }, + + checkAndAddRealtimeData: function(ortb2, data, segtax) { + if (data.rtd) { + if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(ortb2, data.rtd, 504)) { + logMessage('DEBUG(handleInit): rtb Object already added'); + } else { + addRealTimeData(ortb2, data.rtd); + } + logInfo('DEBUG(checkAndAddRealtimeData) - 1'); + } + }, + + checkIfSegmentsAlreadyExist: function(ortb2, rtd, segtax) { + let segmentsExist = false + 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] + if (element.ext && element.ext.segtax == segtax) { + segmentsExist = true + logMessage('DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', ortb2.user.data); + break; + } + } + } + return segmentsExist }, dapLog: function(args) { @@ -248,6 +453,35 @@ export const dapUtils = { logInfo('%cDAP Client', css, args); }, + isValidHttpsUrl: function(urlString) { + let url; + try { + url = new URL(urlString); + } catch (_) { + return false; + } + return url.protocol === 'https:'; + }, + + checkConsent: function(userConsent) { + let consent = true; + + if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) { + const gdpr = userConsent.gdpr; + const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0; + const gdprConsentString = hasGdpr ? gdpr.consentString : ''; + if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { + logError('akamaiDapRtd submodule requires consent string to call API'); + consent = false; + } + } else if (userConsent && userConsent.usp) { + const usp = userConsent.usp; + consent = usp[1] !== 'N' && usp[2] !== 'Y'; + } + + return consent; + }, + /******************************************************************************* * * V2 (And Beyond) API @@ -293,23 +527,23 @@ export const dapUtils = { * function( response, status, xhr } { token = response; }, * function( xhr, status, error ) { ; } // handle error */ - dapTokenize: function(config, identity, onSuccess = null, onError = null) { + dapTokenize: function(config, identity, onDone, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error) {}; + onError = function(xhr, status, error, onDone) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError'); + onError(null, 'Invalid config object', 'ClientError', onDone); return; } if (typeof (config.domain) != 'string') { - onError(null, 'Invalid config.domain: must be a string', 'ClientError'); + onError(null, 'Invalid config.domain: must be a string', 'ClientError', onDone); return; } if (config.domain.length <= 0) { - onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError'); + onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError', onDone); return; } @@ -318,22 +552,22 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); return; } if (identity == null || typeof (identity) == typeof (undefined)) { - onError(null, 'Invalid identity object', 'ClientError'); + onError(null, 'Invalid identity object', 'ClientError', onDone); return; } if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) { - onError(null, "Identity must contain a valid 'type' field", 'ClientError'); + onError(null, "Identity must contain a valid 'type' field", 'ClientError', onDone); return; } @@ -348,6 +582,11 @@ export const dapUtils = { apiParams.attributes = identity.attributes; } + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); + if (entropyDict && entropyDict.entropy) { + apiParams.entropy = entropyDict.entropy; + } + let method; let body; let path; @@ -359,10 +598,16 @@ export const dapUtils = { body = JSON.stringify(apiParams); break; default: - onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError'); + onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError', onDone); return; } + let customHeaders = {'Content-Type': 'application/json'}; + let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); + if (dapSSID) { + customHeaders['Akamai-DAP-SS-ID'] = dapSSID; + } + let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { @@ -373,19 +618,16 @@ export const dapUtils = { token = request.getResponseHeader('Akamai-DAP-Token'); break; } - onSuccess(token, request.status, request); + onSuccess(token, request.status, request, onDone); }, error: (request, error) => { - onError(request, request.statusText, error); + onError(request, request.statusText, error, onDone); } }; ajax(url, cb, body, { method: method, - customHeaders: { - 'Content-Type': 'application/json', - 'Pragma': 'akamai-x-cache-on' - } + customHeaders: customHeaders }); }, @@ -411,7 +653,7 @@ export const dapUtils = { * api_hostname: 'api.dap.akadns.net', * }; * - * // token from dap_x1_tokenize + * // token from dap_tokenize * * dapMembership( config, token, * function( membership, status, xhr ) { @@ -422,13 +664,13 @@ export const dapUtils = { * } ); * */ - dapMembership: function(config, token, onSuccess = null, onError = null) { + dapMembership: function(config, token, onDone, onSuccess = null, onError = null) { if (onError == null) { - onError = function(xhr, status, error) {}; + onError = function(xhr, status, error, onDone) {}; } if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError'); + onError(null, 'Invalid config object', 'ClientError', onDone); return; } @@ -437,32 +679,32 @@ export const dapUtils = { } if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError'); + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); return; } if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError'); + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); return; } if (token == null || typeof (token) != 'string') { - onError(null, 'Invalid token: must be a non-null string', 'ClientError'); + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); return; } let path = '/data-activation/' + - config.api_version + - '/token/' + token + - '/membership'; + config.api_version + + '/token/' + token + + '/membership'; let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { - onSuccess(JSON.parse(response), request.status, request); + onSuccess(JSON.parse(response), request.status, request, onDone); }, error: (error, request) => { - onError(request, request.status, error); + onError(request, request.status, error, onDone); } }; @@ -470,5 +712,91 @@ export const dapUtils = { method: 'GET', customHeaders: {} }); + }, + + /** + * SYNOPSIS + * + * dapEncryptedMembership( config, token, onSuccess, onError ); + * + * DESCRIPTION + * + * Return the audience segment membership along with a new Secure Advertising + * ID for this token in encrypted format. + * + * PARAMETERS + * + * config: an array of system configuration parameters + * + * token: the token previously returned from the tokenize API + * + * EXAMPLE + * + * config = { + * api_hostname: 'api.dap.akadns.net', + * }; + * + * // token from dap_tokenize + * + * dapEncryptedMembership( config, token, + * function( membership, status, xhr ) { + * // Run auction with membership.segments and membership.said after decryption + * }, + * function( xhr, status, error ) { + * // error + * } ); + * + */ + dapEncryptedMembership: function(config, token, onDone, onSuccess = null, onError = null) { + if (onError == null) { + onError = function(xhr, status, error, onDone) {}; + } + + if (config == null || typeof (config) == typeof (undefined)) { + onError(null, 'Invalid config object', 'ClientError', onDone); + return; + } + + if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { + config.api_version = 'x1'; + } + + if (typeof (config.api_version) != 'string') { + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); + return; + } + + if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); + return; + } + + if (token == null || typeof (token) != 'string') { + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); + return; + } + let path = '/data-activation/' + + config.api_version + + '/token/' + token + + '/membership/encrypt'; + + let url = 'https://' + config.api_hostname + path; + + let cb = { + success: (response, request) => { + let encToken = request.getResponseHeader('Akamai-DAP-Token'); + onSuccess(encToken, request.status, request, onDone); + }, + error: (error, request) => { + onError(request, request.status, error, onDone); + } + }; + ajax(url, cb, undefined, { + method: 'GET', + customHeaders: { + 'Content-Type': 'application/json', + 'Pragma': 'akamai-x-get-extracted-values' + } + }); } } diff --git a/modules/akamaiDapRtdProvider.md b/modules/akamaiDapRtdProvider.md index ade11b88602..efd93db3a51 100644 --- a/modules/akamaiDapRtdProvider.md +++ b/modules/akamaiDapRtdProvider.md @@ -17,6 +17,7 @@ ``` pbjs.setConfig({ realTimeData: { + auctionDelay: 2000, dataProviders: [ { name: "dap", @@ -25,9 +26,10 @@ apiHostname: '', apiVersion: "x1", domain: 'your-domain.com', - identityType: 'email' | 'mobile' | ... | 'dap-signature:1.0.0', - segtax: , - tokenTtl: 5, + identityType: 'email' | 'mobile' | ... | 'dap-signature:1.3.0', + segtax: 504, + dapEntropyUrl: 'https://dap-dist.akamaized.net/dapentropy.js', + dapEntropyTimeout: 1500 // Maximum time for dapentropy to run } } ] diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js new file mode 100644 index 00000000000..ac8b5af9533 --- /dev/null +++ b/modules/alkimiBidAdapter.js @@ -0,0 +1,140 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepClone, deepAccess } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'alkimi'; +export const ENDPOINT = 'https://exchange.alkimi-onboarding.com/bid?prebid=true'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.bidFloor && bid.params.token); + }, + + 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) + }) + bidIds.push(bidRequest.bidId) + }) + + const alkimiConfig = config.getConfig('alkimi'); + + let payload = { + requestId: bidderRequest.auctionId, + signRequest: { bids, randomUUID: alkimiConfig && alkimiConfig.randomUUID }, + bidIds, + 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 = { + contentType: 'application/json', + customHeaders: { + 'Rtb-Direct': true + } + } + + return { + method: 'POST', + url: ENDPOINT, + data: payload, + options + }; + }, + + interpretResponse: function (serverResponse, request) { + const serverBody = serverResponse.body; + if (!serverBody || typeof serverBody !== 'object') { + return []; + } + + const { prebidResponse } = serverBody; + if (!prebidResponse || typeof prebidResponse !== 'object') { + return []; + } + + let bids = []; + prebidResponse.forEach(bidResponse => { + let bid = deepClone(bidResponse); + bid.cpm = parseFloat(bidResponse.cpm); + + // banner or video + if (VIDEO === bid.mediaType) { + bid.vastXml = bid.ad; + } + + bid.meta = {}; + bid.meta.advertiserDomains = bid.adomain || []; + + bids.push(bid); + }) + + return bids; + }, + + onBidWon: function (bid) { + let winUrl; + if (bid.winUrl || bid.vastUrl) { + winUrl = bid.winUrl ? bid.winUrl : bid.vastUrl; + winUrl = winUrl.replace(/\$\{AUCTION_PRICE\}/, bid.cpm); + } else if (bid.ad) { + let trackImg = bid.ad.match(/(?!^)/); + bid.ad = bid.ad.replace(trackImg[0], ''); + winUrl = trackImg[0].split('"')[1]; + winUrl = winUrl.replace(/\$%7BAUCTION_PRICE%7D/, bid.cpm); + } else { + return false; + } + + ajax(winUrl, null); + return true; + } +} + +function prepareSizes(sizes) { + return sizes && sizes.map(size => ({ width: size[0], height: size[1] })); +} + +const getFormatType = bidRequest => { + if (deepAccess(bidRequest, 'mediaTypes.banner')) return 'Banner' + if (deepAccess(bidRequest, 'mediaTypes.video')) return 'Video' + if (deepAccess(bidRequest, 'mediaTypes.audio')) return 'Audio' +} + +registerBidder(spec); diff --git a/modules/alkimiBidAdapter.md b/modules/alkimiBidAdapter.md new file mode 100644 index 00000000000..2d1fd42c70f --- /dev/null +++ b/modules/alkimiBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Alkimi Bidder Adapter +Module Type: Bidder Adapter +Maintainer: kalidas@alkimiexchange.com +``` + +# Description + +Connects to Alkimi Bidder for bids. +Alkimi bid adapter supports Banner and Video ads. + +# Test Parameters +``` +const adUnits = [ + { + code: 'banner1', + mediaTypes: { + banner: { // Media Type can be banner or video or ... + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'alkimi', + params: { + bidFloor: 0.5, + token: 'a6b042a5-2d68-4170-a051-77fbaf00203a', // Publisher Token(Id) provided by Alkimi + } + } + ] + } +] +``` 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/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js index 7760aa2b47b..e97a2531def 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; } diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index 421eb99b4c1..a6ab1ea03da 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,23 +1,12 @@ 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 CONFIG = { - 'apacdex': { - 'ENDPOINT': 'https://useast.quantumdex.io/auction/apacdex', - 'USERSYNC': 'https://sync.quantumdex.io/usersync/apacdex' - }, - 'quantumdex': { - 'ENDPOINT': 'https://useast.quantumdex.io/auction/quantumdex', - 'USERSYNC': 'https://sync.quantumdex.io/usersync/quantumdex' - }, - 'valueimpression': { - 'ENDPOINT': 'https://useast.quantumdex.io/auction/adapter', - 'USERSYNC': 'https://sync.quantumdex.io/usersync/adapter' - } -}; +const ENDPOINT = 'https://useast.quantumdex.io/auction/pbjs' +const USERSYNC = 'https://sync.quantumdex.io/usersync/pbjs' -var bidderConfig = CONFIG[BIDDER_CODE]; var bySlotTargetKey = {}; var bySlotSizesCount = {} @@ -56,8 +45,6 @@ export const spec = { let test; let bids = []; - bidderConfig = CONFIG[validBidRequests[0].bidder]; - test = config.getConfig('debug'); validBidRequests.forEach(bidReq => { @@ -118,7 +105,8 @@ export const spec = { payload.site = {}; 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) { @@ -156,13 +144,14 @@ export const spec = { transactionId: bid.transactionId, sizes: bid.sizes, bidId: bid.bidId, + adUnitCode: bid.adUnitCode, bidFloor: bid.bidFloor } }); return { method: 'POST', - url: bidderConfig.ENDPOINT, + url: ENDPOINT, data: payload, withCredentials: true, bidderRequests: bids @@ -209,32 +198,47 @@ export const spec = { }); return bidResponses; }, - getUserSyncs: function (syncOptions, serverResponses) { + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { const syncs = []; - try { - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: bidderConfig.USERSYNC - }); + if (hasPurpose1Consent(gdprConsent)) { + let params = ''; + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + if (typeof gdprConsent.gdprApplies === 'boolean') { + params = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params = `?gdpr_consent=${gdprConsent.consentString}`; + } } - if (serverResponses.length > 0 && serverResponses[0].body && serverResponses[0].body.pixel) { - serverResponses[0].body.pixel.forEach(px => { - if (px.type === 'image' && syncOptions.pixelEnabled) { - syncs.push({ - type: 'image', - url: px.url - }); - } - if (px.type === 'iframe' && syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: px.url - }); - } - }); + if (uspConsent) { + params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; } - } catch (e) { } + + try { + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: USERSYNC + params + }); + } + if (serverResponses.length > 0 && serverResponses[0].body && serverResponses[0].body.pixel) { + serverResponses[0].body.pixel.forEach(px => { + if (px.type === 'image' && syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: px.url + params + }); + } + if (px.type === 'iframe' && syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: px.url + params + }); + } + }); + } + } catch (e) { } + } return syncs; } }; @@ -282,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; } /** @@ -303,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; } /** 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 2758fc2d03a..21c455009fe 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -21,7 +21,8 @@ import { logInfo, logMessage, logWarn, - transformBidderParamKeywords + transformBidderParamKeywords, + getWindowFromDocument } from '../src/utils.js'; import {Renderer} from '../src/Renderer.js'; import {config} from '../src/config.js'; @@ -32,6 +33,7 @@ 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'; const BIDDER_CODE = 'appnexus'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -91,7 +93,6 @@ export const spec = { gvlid: GVLID, aliases: [ { code: 'appnexusAst', gvlid: 32 }, - { code: 'brealtime' }, { code: 'emxdigital', gvlid: 183 }, { code: 'pagescience' }, { code: 'defymedia' }, @@ -99,7 +100,6 @@ export const spec = { { code: 'matomy' }, { code: 'featureforward' }, { code: 'oftmedia' }, - { code: 'districtm', gvlid: 144 }, { code: 'adasta' }, { code: 'beintoo', gvlid: 618 }, ], @@ -266,11 +266,16 @@ export const spec = { 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 = bidderRequest.refererInfo.canonicalUrl; + if (isStr(pubPageUrl) && pubPageUrl !== '') { + refererinfo.rd_can = pubPageUrl; + } payload.referrer_detection = refererinfo; } @@ -287,7 +292,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); @@ -381,7 +385,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' @@ -389,11 +393,22 @@ export const spec = { } }, - transformBidParams: function (params, isOpenRtb) { + transformBidParams: function (params, isOpenRtb, adUnit, bidRequests) { let conversionFn = transformBidderParamKeywords; if (isOpenRtb === true) { + let s2sEndpointUrl = null; let s2sConfig = config.getConfig('s2sConfig'); - let s2sEndpointUrl = deepAccess(s2sConfig, 'endpoint.p1Consent'); + + if (isPlainObject(s2sConfig)) { + s2sEndpointUrl = deepAccess(s2sConfig, 'endpoint.p1Consent'); + } else if (isArray(s2sConfig)) { + s2sConfig.forEach(s2sCfg => { + if (includes(s2sCfg.bidders, adUnit.bids[0].bidder)) { + s2sEndpointUrl = deepAccess(s2sCfg, 'endpoint.p1Consent'); + } + }) + } + if (s2sEndpointUrl && s2sEndpointUrl.match('/openrtb2/prebid')) { conversionFn = convertKeywordsToString; } @@ -432,7 +447,7 @@ export const spec = { * @param {Bid} bid */ onBidWon: function (bid) { - if (bid.native) { + if (FEATURES.NATIVE && bid.native) { reloadViewabilityScriptWithCorrectParameters(bid); } } @@ -533,16 +548,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,7 +556,7 @@ function formatRequest(payload, bidderRequest) { let endpointUrl = URL; - if (!hasPurpose1Consent(bidderRequest)) { + if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { endpointUrl = URL_SIMPLE; } @@ -699,7 +704,10 @@ function newBid(serverBid, rtbBid, bidderRequest) { if (rtbBid.renderer_url) { const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid); - const rendererOptions = deepAccess(videoBid, 'renderer.options'); + let rendererOptions = deepAccess(videoBid, 'mediaTypes.video.renderer.options'); // mediaType definition has preference (shouldn't options be .config?) + if (!rendererOptions) { + rendererOptions = deepAccess(videoBid, 'renderer.options'); // second the adUnit definition has preference (shouldn't options be .config?) + } bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); } break; @@ -707,7 +715,7 @@ 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]; // setting up the jsTracker: @@ -802,6 +810,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; @@ -838,7 +853,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]); @@ -1120,9 +1135,13 @@ function buildNativeRequest(params) { * @param {string} elementId element id */ function hidedfpContainer(elementId) { - var el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); - if (el[0]) { - el[0].style.setProperty('display', 'none'); + try { + const el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); + if (el[0]) { + el[0].style.setProperty('display', 'none'); + } + } catch (e) { + // element not found! } } @@ -1138,12 +1157,13 @@ function hideSASIframe(elementId) { } } -function outstreamRender(bid) { +function outstreamRender(bid, doc) { hidedfpContainer(bid.adUnitCode); hideSASIframe(bid.adUnitCode); // push to render queue because ANOutstreamVideo may not be loaded yet bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ + const win = getWindowFromDocument(doc) || window; + win.ANOutstreamVideo.renderAd({ tagId: bid.adResponse.tag_id, sizes: [bid.getSize().split('x')], targetId: bid.adUnitCode, // target div id to render video 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 559afefa94b..abe0cf907ed 100644 --- a/modules/asealBidAdapter.js +++ b/modules/asealBidAdapter.js @@ -1,30 +1,78 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { generateUUID, getWindowTop, getWindowSelf } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; export const BIDDER_CODE = 'aseal'; -const SUPPORTED_AD_TYPES = [BANNER]; +export const SUPPORTED_AD_TYPES = [BANNER]; export const API_ENDPOINT = 'https://tkprebid.aotter.net/prebid/adapter'; -export const HEADER_AOTTER_VERSION = 'prebid_0.0.1'; +export const WEB_SESSION_ID_KEY = '__tkwsid'; +export const HEADER_AOTTER_VERSION = 'prebid_0.0.2'; + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +const getTrekWebSessionId = () => { + let wsid = + storage.localStorageIsEnabled() && + storage.getDataFromLocalStorage(WEB_SESSION_ID_KEY); + + if (!wsid) { + wsid = generateUUID(); + setTrekWebSessionId(wsid); + } + + return wsid; +}; + +const setTrekWebSessionId = (wsid) => { + if (storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage(WEB_SESSION_ID_KEY, wsid); + } +}; + +const canAccessTopWindow = () => { + try { + return !!getWindowTop().location.href; + } catch (errro) { + return false; + } +}; export const spec = { code: BIDDER_CODE, aliases: ['aotter', 'trek'], supportedMediaTypes: SUPPORTED_AD_TYPES, - - isBidRequestValid: (bid) => !!bid.params.placeUid && typeof bid.params.placeUid === 'string', - + isBidRequestValid: (bid) => + !!bid.params.placeUid && typeof bid.params.placeUid === 'string', buildRequests: (validBidRequests, bidderRequest) => { if (validBidRequests.length === 0) { return []; } - const clientId = - config.getConfig('aseal.clientId') || ''; + const clientId = config.getConfig('aseal.clientId') || ''; + + const windowTop = getWindowTop(); + const windowSelf = getWindowSelf(); + + const w = canAccessTopWindow() ? windowTop : windowSelf; 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(), + }, + payload: { + meta: { + dr: w.document.referrer, + drs: windowSelf.document.referrer, + drt: (canAccessTopWindow() && windowTop.document.referrer) || '', + dt: w.document.title, + dl: w.location.href, + }, + }, }; const options = { @@ -36,14 +84,15 @@ export const spec = { }, }; - return [{ - method: 'POST', - url: API_ENDPOINT, - data, - options, - }]; + return [ + { + method: 'POST', + url: API_ENDPOINT, + data, + options, + }, + ]; }, - interpretResponse: (serverResponse, bidRequest) => { if (!Array.isArray(serverResponse.body)) { return []; 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..390e06366d1 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.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 2c100bce27b..754a48ede75 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -1,32 +1,30 @@ -import { deepAccess, isFn, logError, getValue, getBidIdParameter, _each, isArray, triggerPixel } from '../src/utils.js'; +import { + deepAccess, + isFn, + logError, + getValue, + getBidIdParameter, + _each, + isArray, + triggerPixel, + formatQS, +} from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import { createEidsArray } from './userId/eids.js'; const BIDDER_CODE = 'audiencerun'; const BASE_URL = 'https://d.audiencerun.com'; const AUCTION_URL = `${BASE_URL}/prebid`; const TIMEOUT_EVENT_URL = `${BASE_URL}/ps/pbtimeout`; +const ERROR_EVENT_URL = `${BASE_URL}/js_log`; const DEFAULT_CURRENCY = 'USD'; let requestedBids = []; /** - * Gets bidder request referer - * - * @param {Object} bidderRequest - * @return {string} - */ -function getPageUrl(bidderRequest) { - return ( - config.getConfig('pageUrl') || - deepAccess(bidderRequest, 'refererInfo.referer') || - null - ); -} - -/** - * Returns bidfloor through floors module if available + * Returns bidfloor through floors module if available. * * @param {Object} bid * @returns {number} @@ -44,19 +42,48 @@ function getBidFloor(bid) { }); return bidFloor.floor; } catch (_) { - return 0 + return 0; } } +/** + * Returns the most top page referer. + * + * @returns {string} + */ +function getPageReferer() { + let t, e; + do { + t = t ? t.parent : window; + try { + e = t.document.referrer; + } catch (_) { + break; + } + } while (t !== window.top); + return e; +} + +/** + * Returns bidder request page url. + * + * @param {Object} bidderRequest + * @return {string} + */ +function getPageUrl(bidderRequest) { + return bidderRequest?.refererInfo?.page +} + export const spec = { - version: '1.1.0', + version: '1.2.0', code: BIDDER_CODE, + gvlid: 944, supportedMediaTypes: [BANNER], /** * Determines whether or not the given bid request is valid. * - * @param {object} bid The bid to validate. + * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { @@ -95,12 +122,21 @@ export const spec = { const payload = { libVersion: this.version, - referer: getPageUrl(bidderRequest), + 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.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, }; + payload.uspConsent = deepAccess(bidderRequest, 'uspConsent'); + payload.schain = deepAccess(bidRequests, '0.schain'); + payload.userId = deepAccess(bidRequests, '0.userId') ? createEidsArray(bidRequests[0].userId) : []; + if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr = { consent: bidderRequest.gdprConsent.consentString, @@ -117,7 +153,7 @@ export const spec = { return { method: 'POST', - url: AUCTION_URL, + url: deepAccess(bidRequests, '0.params.auctionUrl', AUCTION_URL), data: JSON.stringify(payload), options: { withCredentials: true, @@ -201,7 +237,9 @@ export const spec = { } timeoutData.forEach((bid) => { - const bidOnTimeout = requestedBids.find((requestedBid) => requestedBid.bidId === bid.bidId); + const bidOnTimeout = requestedBids.find( + (requestedBid) => requestedBid.bidId === bid.bidId + ); if (bidOnTimeout) { triggerPixel( @@ -210,6 +248,18 @@ export const spec = { } }); }, + + /** + * Registers bidder specific code, which will execute if the bidder responded with an error. + * @param {{bidderRequest: object}} args An object from which we extract bidderRequest object. + */ + onBidderError: function ({ bidderRequest }) { + const queryString = formatQS({ + message: `Prebid.js: Server call for ${bidderRequest.bidderCode} failed.`, + url: encodeURIComponent(getPageUrl(bidderRequest)), + }); + triggerPixel(`${ERROR_EVENT_URL}/?${queryString}`); + }, }; registerBidder(spec); diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js index 726bbef9bd6..1174c2a9f38 100644 --- a/modules/automatadBidAdapter.js +++ b/modules/automatadBidAdapter.js @@ -18,7 +18,7 @@ export const spec = { isBidRequestValid: function (bid) { // will receive request bid. check if have necessary params for bidding - return (bid && bid.hasOwnProperty('params') && bid.params.hasOwnProperty('siteId') && bid.params.hasOwnProperty('placementId') && bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty('banner')) + return (bid && bid.hasOwnProperty('params') && bid.params.hasOwnProperty('siteId') && bid.params.siteId != null && bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty('banner') && typeof bid.mediaTypes.banner == 'object') }, buildRequests: function (validBidRequests, bidderRequest) { @@ -29,16 +29,29 @@ export const spec = { const siteId = validBidRequests[0].params.siteId const impressions = validBidRequests.map(bidRequest => { - return { - id: bidRequest.bidId, - adUnitCode: bidRequest.adUnitCode, - placement: bidRequest.params.placementId, - banner: { - format: bidRequest.sizes.map(sizeArr => ({ - w: sizeArr[0], - h: sizeArr[1], - })) - }, + if (bidRequest.params.hasOwnProperty('placementId')) { + return { + id: bidRequest.bidId, + adUnitCode: bidRequest.adUnitCode, + placement: bidRequest.params.placementId, + banner: { + format: bidRequest.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1], + })) + }, + } + } else { + return { + id: bidRequest.bidId, + adUnitCode: bidRequest.adUnitCode, + banner: { + format: bidRequest.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1], + })) + }, + } } }) @@ -48,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 }, } @@ -61,7 +74,7 @@ export const spec = { data: payloadString, options: { contentType: 'application/json', - withCredentials: false, + withCredentials: true, crossOrigin: true, }, } @@ -101,7 +114,7 @@ export const spec = { }, onTimeout: function(timeoutData) { const timeoutUrl = ENDPOINT_URL + '/timeout' - ajax(timeoutUrl, null, JSON.stringify(timeoutData)) + spec.ajaxCall(timeoutUrl, null, JSON.stringify(timeoutData), {method: 'POST', withCredentials: true}) }, onBidWon: function(bid) { if (!bid.nurl) { return } @@ -123,11 +136,15 @@ export const spec = { /\$\{AUCTION_ID\}/, bid.auctionId ) - spec.ajaxCall(winUrl, null) + spec.ajaxCall(winUrl, null, null, {method: 'GET', withCredentials: true}) return true }, - ajaxCall: function(endpoint, data) { - ajax(endpoint, data) + + ajaxCall: function(endpoint, callback, data, options = {}) { + if (data) { + options.contentType = 'application/json' + } + ajax(endpoint, callback, data, options) }, } diff --git a/modules/automatadBidAdapter.md b/modules/automatadBidAdapter.md index 56a4b53c067..94bc707c75b 100644 --- a/modules/automatadBidAdapter.md +++ b/modules/automatadBidAdapter.md @@ -25,8 +25,8 @@ var adUnits = [ bids: [{ bidder: 'automatad', params: { - siteId: 'someValue', - placementId: 'someValue' + siteId: 'someValue', // required + placementId: 'someValue' // optional } }] } 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 2e74170fcaf..b4ac0f2a5a3 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -1,4 +1,5 @@ import { deepAccess, isArray, logWarn, triggerPixel, buildUrl, logInfo, getValue, getBidIdParameter } from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'beop'; @@ -36,18 +37,17 @@ export const spec = { */ buildRequests: function(validBidRequests, bidderRequest) { const slots = validBidRequests.map(beOpRequestSlotsMaker); - let pageUrl = deepAccess(window, 'location.href') || deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl'); - let fpd = config.getLegacyFpd(config.getConfig('ortb2')); - let gdpr = bidderRequest.gdprConsent; - let firstSlot = slots[0]; - let payloadObject = { + const pageUrl = getPageUrl(bidderRequest.refererInfo, window); + const gdpr = bidderRequest.gdprConsent; + const firstSlot = slots[0]; + const payloadObject = { at: new Date().toString(), nid: firstSlot.nid, nptnid: firstSlot.nptnid, 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,6 +100,7 @@ export const spec = { function buildTrackingParams(data, info, value) { const accountId = data.params.accountId; + const pageUrl = getPageUrl(null, window); return { pid: accountId === undefined ? data.ad.match(/account: \“([a-f\d]{24})\“/)[1] : accountId, nid: data.params.networkId, @@ -110,7 +111,7 @@ function buildTrackingParams(data, info, value) { se_ca: 'bid', se_ac: info, se_va: value, - url: window.location.href + url: pageUrl }; } @@ -141,4 +142,47 @@ function beOpRequestSlotsMaker(bid) { } } +const protocolRelativeRegExp = /^\/\// +function isProtocolRelativeUrl(url) { + return url && url.match(protocolRelativeRegExp) != null; +} + +const withProtocolRegExp = /[a-z]{1,}:\/\// +function isNoProtocolUrl(url) { + return url && url.match(withProtocolRegExp) == null; +} + +function ensureProtocolInUrl(url, defaultProtocol) { + if (isProtocolRelativeUrl(url)) { + return `${defaultProtocol}${url}`; + } else if (isNoProtocolUrl(url)) { + return `${defaultProtocol}//${url}`; + } + return url; +} + +/** + * sometimes trying to access a field (protected?) triggers an exception + * Ex deepAccess(window, 'top.location.href') might throw if it crosses origins + * so here is a lenient version + */ +function safeDeepAccess(obj, path) { + try { + return deepAccess(obj, path) + } catch (_e) { + return null; + } +} + +function getPageUrl(refererInfo, window) { + refererInfo = refererInfo || getRefererInfo(); + let pageUrl = refererInfo.canonicalUrl || safeDeepAccess(window, 'top.location.href') || deepAccess(window, 'location.href'); + // Ensure the protocol is present (looks like sometimes the extracted pageUrl misses it) + if (pageUrl != null) { + const defaultProtocol = safeDeepAccess(window, 'top.location.protocol') || deepAccess(window, 'location.protocol'); + pageUrl = ensureProtocolInUrl(pageUrl, defaultProtocol); + } + return pageUrl; +} + registerBidder(spec); diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index 04dccf563e6..9e57d0f5cd3 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; @@ -53,7 +52,7 @@ export const spec = { params.maxd = video.maxd; params.mind = video.mind; params.pos = 'atf'; - ENDPOINT += '&jst=pvc'; + params.jst = 'pvc'; params.codeType = includes(CODE_TYPES, video.codeType) ? video.codeType : 'inpage'; } @@ -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') { @@ -118,7 +118,7 @@ export const spec = { mediaType: serverResponse.body[i].mediaType, ttl: serverResponse.body[i].ttl, creativeId: serverResponse.body[i].creativeid, - currency: serverResponse.body[i].currency || 'RUB', + currency: serverResponse.body[i].currency || 'USD', netRevenue: serverResponse.body[i].netRevenue || true, ad: serverResponse.body[i].ad, meta: { @@ -158,10 +158,16 @@ export const spec = { // type: 'iframe', // url: 'https://acdn.adnxs.com/dmp/async_usersync.html' // }); - syncs.push({ - type: 'iframe', - url: 'https://ads.betweendigital.com/sspmatch-iframe' - }); + syncs.push( + { + type: 'iframe', + url: 'https://ads.betweendigital.com/sspmatch-iframe' + }, + { + type: 'image', + url: 'https://ads.betweendigital.com/sspmatch' + } + ); return syncs; } } 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/biddoBidAdapter.js b/modules/biddoBidAdapter.js new file mode 100644 index 00000000000..5512ca60f8e --- /dev/null +++ b/modules/biddoBidAdapter.js @@ -0,0 +1,92 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'biddo'; +const ENDPOINT_URL = 'https://ad.adopx.net/delivery/impress'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bidRequest The bid request params to validate. + * @return boolean True if this is a valid bid request, and false otherwise. + */ + isBidRequestValid: function(bidRequest) { + return !!bidRequest.params.zoneId; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {Array} validBidRequests an array of bid requests + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests) { + let serverRequests = []; + + validBidRequests.forEach(bidRequest => { + const sizes = bidRequest.mediaTypes.banner.sizes; + + sizes.forEach(([width, height]) => { + bidRequest.params.requestedSizes = [width, height]; + + const payload = { + ctype: 'div', + pzoneid: bidRequest.params.zoneId, + width, + height, + }; + + const payloadString = Object.keys(payload).map(k => k + '=' + encodeURIComponent(payload[k])).join('&'); + + serverRequests.push({ + method: 'GET', + url: ENDPOINT_URL, + data: payloadString, + bidderRequest: bidRequest, + }); + }); + }); + + return serverRequests; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidderRequest A matched bid request for this response. + * @return Array An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, {bidderRequest}) { + const response = serverResponse.body; + const bidResponses = []; + + if (response && response.template && response.template.html) { + const {bidId} = bidderRequest; + const [width, height] = bidderRequest.params.requestedSizes; + + const bidResponse = { + requestId: bidId, + cpm: response.hb.cpm, + creativeId: response.banner.hash, + currency: 'USD', + netRevenue: response.hb.netRevenue, + ttl: 600, + ad: response.template.html, + mediaType: 'banner', + meta: { + advertiserDomains: response.hb.adomains || [], + }, + width, + height, + }; + + bidResponses.push(bidResponse); + } + + return bidResponses; + }, +} + +registerBidder(spec); diff --git a/modules/biddoBidAdapter.md b/modules/biddoBidAdapter.md new file mode 100644 index 00000000000..baea44b22f2 --- /dev/null +++ b/modules/biddoBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: Biddo Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@biddo.net +``` + +# Description + +Module that connects to Invamia demand sources. + +# Test Parameters + +``` + const adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [{ + bidder: 'biddo', + params: { + zoneId: 7254, + }, + }], + }]; +``` 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/bidwatchAnalyticsAdapter.js b/modules/bidwatchAnalyticsAdapter.js new file mode 100644 index 00000000000..26a8c370af3 --- /dev/null +++ b/modules/bidwatchAnalyticsAdapter.js @@ -0,0 +1,90 @@ +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import { ajax } from '../src/ajax.js'; + +const analyticsType = 'endpoint'; +const url = 'URL_TO_SERVER_ENDPOINT'; + +const { + EVENTS: { + AUCTION_END, + BID_WON, + } +} = CONSTANTS; + +let allEvents = {} +let initOptions = {} +let endpoint = 'https://default' +let objectToSearchForBidderCode = ['bidderRequests', 'bidsReceived', 'noBids'] + +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(); } + }); + return arg +} + +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]]) } + } + } + 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 +} + +function addEvent(eventType, args) { + if (allEvents[eventType] == undefined) { allEvents[eventType] = [] } + if (eventType && args) { args = checkBidderCode(args); } + allEvents[eventType].push(args); +} + +function handleBidWon(args) { + if (typeof allEvents.bidRequested == 'object' && allEvents.bidRequested.length > 0 && allEvents.bidRequested[0].gdprConsent) { args.gdpr = allEvents.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}); +} + +let bidwatchAnalytics = Object.assign(adapter({url, analyticsType}), { + track({ + eventType, + args + }) { + addEvent(eventType, args); + switch (eventType) { + case AUCTION_END: + handleAuctionEnd(); + break; + case BID_WON: + handleBidWon(args); + break; + } + }}); + +// save the base class function +bidwatchAnalytics.originEnableAnalytics = bidwatchAnalytics.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +bidwatchAnalytics.enableAnalytics = function (config) { + bidwatchAnalytics.originEnableAnalytics(config); // call the base class function + initOptions = config.options; + if (initOptions.domain) { endpoint = 'https://' + initOptions.domain; } +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: bidwatchAnalytics, + code: 'bidwatch' +}); + +export default bidwatchAnalytics; diff --git a/modules/bidwatchAnalyticsAdapter.md b/modules/bidwatchAnalyticsAdapter.md new file mode 100644 index 00000000000..bfa453640b8 --- /dev/null +++ b/modules/bidwatchAnalyticsAdapter.md @@ -0,0 +1,21 @@ +# Overview +Module Name: bidwatch Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: tech@bidwatch.io + +# Description + +Analytics adapter for bidwatch.io. + +# Test Parameters + +``` +{ + provider: 'bidwatch', + options : { + domain: 'test.endpoint' + } +} +``` diff --git a/modules/big-richmediaBidAdapter.js b/modules/big-richmediaBidAdapter.js index cd8b2462eb8..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.4.0', + version: '1.5.1', code: BIDDER_CODE, gvlid: baseAdapter.GVLID, // use base adapter gvlid supportedMediaTypes: [ BANNER, VIDEO ], @@ -78,6 +78,14 @@ export const spec = { customSelector, isReplayable }; + + // 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') { + bid.width = 1 + bid.height = 1 + } + const encoded = window.btoa(JSON.stringify(renderParams)); bid.ad = ` `; diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 6223626834d..ce961867c59 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -63,7 +63,7 @@ export const spec = { 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; @@ -95,6 +95,7 @@ export const spec = { }, regs: { coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} }, user: { ext: {} @@ -106,25 +107,15 @@ export const spec = { imp: [impObject], }; - if (bidderRequest && bidderRequest.uspConsent) { - data.regs.ext.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - data.regs.ext.gdpr = gdprApplies ? 1 : 0; - data.user.ext.consent = consentString; - } - - if (bidRequest.schain) { - deepSetValue(data, 'source.ext.schain', bidRequest.schain); - } - let connection = navigator.connection || navigator.webkitConnection; if (connection && connection.effectiveType) { data.device.connectiontype = connection.effectiveType; } if (bidRequest) { + if (bidRequest.schain) { + deepSetValue(data, 'source.ext.schain', bidRequest.schain); + } + if (bidRequest.gdprConsent && bidRequest.gdprConsent.gdprApplies) { deepSetValue(data, 'regs.ext.gdpr', bidRequest.gdprConsent.gdprApplies ? 1 : 0); deepSetValue(data, 'user.ext.consent', bidRequest.gdprConsent.consentString); diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 45b6c46c2df..495862013de 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..33e99211e9b 100644 --- a/modules/boldwinBidAdapter.js +++ b/modules/boldwinBidAdapter.js @@ -51,8 +51,9 @@ export const spec = { buildRequests: (validBidRequests = [], bidderRequest) => { 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/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..d29d58a2129 100644 --- a/modules/braveBidAdapter.js +++ b/modules/braveBidAdapter.js @@ -62,23 +62,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..6af7f4fc8a0 100644 --- a/modules/bridgewellBidAdapter.js +++ b/modules/bridgewellBidAdapter.js @@ -72,7 +72,7 @@ export const spec = { let topUrl = ''; if (bidderRequest && bidderRequest.refererInfo) { - topUrl = bidderRequest.refererInfo.referer; + topUrl = bidderRequest.refererInfo.page; } return { @@ -85,9 +85,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 +290,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..bfe1e8ecb29 100644 --- a/modules/brightMountainMediaBidAdapter.js +++ b/modules/brightMountainMediaBidAdapter.js @@ -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/byDataAnalyticsAdapter.js b/modules/byDataAnalyticsAdapter.js index ef6e1a503ee..1bf47cd467f 100644 --- a/modules/byDataAnalyticsAdapter.js +++ b/modules/byDataAnalyticsAdapter.js @@ -5,38 +5,80 @@ import enc from 'crypto-js/enc-utf8'; import adapter from '../src/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.md b/modules/c1xBidAdapter.md deleted file mode 100644 index 83a4ff1ea81..00000000000 --- a/modules/c1xBidAdapter.md +++ /dev/null @@ -1,32 +0,0 @@ -# Overview - -Module Name: C1X Bidder Adapter -Module Type: Bidder Adapter -Maintainer: cathy@c1exchange.com - -# Description - -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 - } - } - ] - }, - ]; -``` \ 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/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index 3fda9917715..f7d74c0df64 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, @@ -111,7 +109,7 @@ export const spec = { 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/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 e3d3c65a4f0..ce366cbecc8 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -4,7 +4,7 @@ import { BANNER } from '../src/mediaTypes.js' import { config } from '../src/config.js' const BIDDER_CODE = 'cointraffic'; -const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp'; +const ENDPOINT_URL = 'https://apps-pbd.ctengine.io/pb/tmp'; const DEFAULT_CURRENCY = 'EUR'; const ALLOWED_CURRENCIES = [ 'EUR', 'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'ISK', 'NOK', 'HRK', 'RUB', 'TRY', @@ -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 94265617d8f..cd0721fa80f 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -61,12 +61,34 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { - const winTop = getWindowTop(); - const location = winTop.location; + let deviceWidth = 0; + let deviceHeight = 0; + let winLocation; + + try { + const winTop = getWindowTop(); + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo?.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + + // TODO: does the fallback to window.location make sense? + const location = refferLocation || winLocation; let placements = []; let request = { - deviceWidth: winTop.screen.width, - deviceHeight: winTop.screen.height, + deviceWidth, + deviceHeight, language: (navigator && navigator.language) ? navigator.language : '', secure: location.protocol === 'https:' ? 1 : 0, host: location.host, @@ -86,29 +108,16 @@ export const spec = { for (let i = 0; i < validBidRequests.length; i++) { let bid = validBidRequests[i]; - let traff = bid.params.traffic || BANNER + let traff = bid.params.traffic || BANNER; let placement = { placementId: bid.params.placement_id, groupId: bid.params.group_id, bidId: bid.bidId, - sizes: bid.mediaTypes[traff].sizes, traffic: traff, eids: [], floor: {} }; - if (typeof bid.getFloor === 'function') { - let tmpFloor = {}; - for (let size of placement.sizes) { - tmpFloor = bid.getFloor({ - currency: 'USD', - mediaType: traff, - size: size - }); - if (tmpFloor) { - placement.floor[`${size[0]}x${size[1]}`] = tmpFloor.floor; - } - } - } + if (bid.schain) { placement.schain = bid.schain; } @@ -125,7 +134,10 @@ export const spec = { rtiPartner: 'TDID' }); } - if (traff === VIDEO) { + 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; @@ -142,6 +154,20 @@ export const spec = { placement.api = bid.mediaTypes[VIDEO].api; placement.linearity = bid.mediaTypes[VIDEO].linearity; } + if (typeof bid.getFloor === 'function') { + let tmpFloor = {}; + for (let size of placement.sizes) { + tmpFloor = bid.getFloor({ + currency: 'USD', + mediaType: traff, + size: size + }); + if (tmpFloor) { + placement.floor[`${size[0]}x${size[1]}`] = tmpFloor.floor; + } + } + } + placements.push(placement); } return { diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js index 77f918276bc..e439e72d1d9 100644 --- a/modules/compassBidAdapter.js +++ b/modules/compassBidAdapter.js @@ -126,14 +126,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/concertBidAdapter.js b/modules/concertBidAdapter.js index 99e2492fb94..398248bfeab 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -36,7 +36,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), @@ -57,7 +57,7 @@ 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 } return slot; 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 65e0d6e92eb..f7ae575a7ae 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 = { @@ -34,37 +30,28 @@ const cmpCallMap = { /** * This function reads the consent string from the config to obtain the consent information of the user. - * @param {function(string)} cmpSuccess acts as a success callback when the value is read from config; pass along consentObject (string) from CMP - * @param {function(string)} cmpError acts as an error callback while interacting with the config string; pass along an error message (string) - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + * @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP */ -function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) { - cmpSuccess(staticConsentData, hookConfig); +function lookupStaticConsentData({onSuccess, onError}) { + processCmpData(staticConsentData, {onSuccess, onError}) } /** * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function * based on the appropriate result. - * @param {function(string)} cmpSuccess acts as a success callback when CMP returns a value; pass along consentObject (string) from CMP - * @param {function(string)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + * @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) */ -function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { +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; } @@ -73,15 +60,6 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { // 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; } @@ -96,45 +74,24 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { }; } - 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') { - cmpSuccess(tcfData, hookConfig); + processCmpData(tcfData, {onSuccess, onError}); + } else { + provisionalConsent = tcfData; } } else { - cmpError('CMP unable to register callback function. Please check CMP setup.', hookConfig); + 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); - cmpSuccess(cmpResponse, hookConfig); - } - } - - 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 cmpError('CMP not found.', hookConfig); + return onError('CMP not found.'); } // to collect the consent information from the user, we perform two calls to the CMP in parallel: // first to collect the user's consent choices represented in an encoded string (via getConsentData) @@ -146,110 +103,47 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { // 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); - } - } - - // find sizes from adUnits object - let adUnits = hookConfig.adUnits; - let width = 1; - let height = 1; - if (Array.isArray(adUnits) && adUnits.length > 0) { - let sizes = getAdUnitSizes(adUnits[0]); - width = sizes[0][0]; - height = sizes[0][1]; - } - - 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') { cmpCallbacks[payload.callId](payload.returnValue, payload.success); @@ -260,208 +154,145 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) { } /** - * 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 - * data as part of a gdprConsent object which gets transferred to adapterManager's gdprDataHandler object. - * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. - * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. - * @param {function} fn required; The next function in the chain, used by hook.js + * Look up consent data and store it in the `consentData` global as well as `adapterManager.js`' gdprDataHandler. + * + * @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. */ -export function requestBidsHook(fn, reqBidsConfigObj) { - // preserves all module related variables for the current auction instance (used primiarily for concurrent auctions) - const hookConfig = { - context: this, - args: [reqBidsConfigObj], - nextFn: fn, - adUnits: reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits, - bidsBackHandler: reqBidsConfigObj.bidsBackHandler, - haveExited: false, - timer: null - }; +function loadConsentData(cb) { + let isDone = false; + let timer = null; - // in case we already have consent (eg during bid refresh) - if (consentData) { - logInfo('User consent information already known. Pulling internally stored information...'); - return exitModule(null, hookConfig); + function done(consentData, shouldCancelAuction, errMsg, ...extraArgs) { + if (timer != null) { + clearTimeout(timer); + } + isDone = true; + gdprDataHandler.setConsentData(consentData); + if (typeof cb === 'function') { + cb(shouldCancelAuction, errMsg, ...extraArgs); + } } if (!includes(Object.keys(cmpCallMap), userCMP)) { - logWarn(`CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); - gdprDataHandler.setConsentData(null); - return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); + done(null, false, `CMP framework (${userCMP}) is not a supported framework. Aborting consentManagement module and resuming auction.`); + return; } - cmpCallMap[userCMP].call(this, processCmpData, cmpFailed, hookConfig); + const callbacks = { + onSuccess: (data) => done(data, false), + onError: function (msg, ...extraArgs) { + done(null, true, msg, ...extraArgs); + } + } + cmpCallMap[userCMP](callbacks); - // only let this code run if module is still active (ie if the callbacks used by CMPs haven't already finished) - if (!hookConfig.haveExited) { + 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, hookConfig); + onTimeout(); } else { - hookConfig.timer = setTimeout(cmpTimedOut.bind(null, hookConfig), consentTimeout); + timer = setTimeout(onTimeout, consentTimeout); } } } /** - * This function checks the consent data provided by CMP to ensure it's in an expected state. - * If it's bad, we exit the module depending on config settings. - * If it's good, then we store the value and exits the module. - * @param {object} consentObject required; object returned by CMP that contains user's consent choices - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + * Like `loadConsentData`, but cache and re-use previously loaded data. + * @param cb */ -function processCmpData(consentObject, hookConfig) { - 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() { - // 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; - return !!( - (typeof gdprApplies !== 'boolean') || - (gdprApplies === true && !isStr(tcString)) - ); +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); } +} - // 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; +/** + * 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 + * data as part of a gdprConsent object which gets transferred to adapterManager's gdprDataHandler object. + * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. + * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * @param {function} fn required; The next function in the chain, used by hook.js + */ +export function requestBidsHook(fn, reqBidsConfigObj) { + loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { + if (errMsg) { + let log = logWarn; + if (shouldCancelAuction) { + log = logError; + errMsg = `${errMsg} Canceling auction as per consentManagement config.`; + } + log(errMsg, ...extraArgs); } - } - - // determine which set of checks to run based on cmpVersion - let checkFn = (cmpVersion === 1) ? checkV1Data : (cmpVersion === 2) ? checkV2Data : null; - // 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}).`); - } - - if (isFn(checkFn)) { - if (checkFn(consentObject)) { - cmpFailed(`CMP returned unexpected value during lookup process.`, hookConfig, consentObject); + if (shouldCancelAuction) { + if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { + reqBidsConfigObj.bidsBackHandler(); + } else { + logError('Error executing bidsBackHandler'); + } } else { - clearTimeout(hookConfig.timer); - storeConsentData(consentObject); - exitModule(null, hookConfig); + fn.call(this, reqBidsConfigObj); } - } else { - cmpFailed('Unable to derive CMP version to process data. Consent object does not conform to TCF v1 or v2 specs.', hookConfig, consentObject); - } + }); } /** - * General timeout callback when interacting with CMP takes too long. + * This function checks the consent data provided by CMP to ensure it's in an expected state. + * If it's bad, we call `onError` + * If it's good, then we store the value and call `onSuccess` */ -function cmpTimedOut(hookConfig) { - if (cmpVersion === 2) { - logWarn(`No response from CMP, continuing auction...`) - storeConsentData(undefined); - exitModule(null, hookConfig) - } else { - cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig); +function processCmpData(consentObject, {onSuccess, onError}) { + function checkData() { + // if CMP does not respond with a gdprApplies boolean, use defaultGdprScope (gdprScope) + const gdprApplies = consentObject && typeof consentObject.gdprApplies === 'boolean' ? consentObject.gdprApplies : gdprScope; + const tcString = consentObject && consentObject.tcString; + return !!( + (typeof gdprApplies !== 'boolean') || + (gdprApplies === true && (!tcString || !isStr(tcString))) + ); } -} -/** - * This function contains the controlled steps to perform when there's a problem with CMP. - * @param {string} errMsg required; should be a short descriptive message for why the failure/issue happened. - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) - * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging -*/ -function cmpFailed(errMsg, hookConfig, extraArgs) { - clearTimeout(hookConfig.timer); - - // still set the consentData to undefined when there is a problem as per config options - if (allowAuction.value && cmpVersion === 1) { - storeConsentData(undefined); + // do extra things for static config + if (userCMP === 'static') { + consentObject = consentObject.getTCData; } - exitModule(errMsg, hookConfig, extraArgs); -} -/** - * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanager.js for later in the auction - * @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 - }; + if (checkData()) { + onError(`CMP returned unexpected value during lookup process.`, consentObject); } 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; - }; + onSuccess(storeConsentData(consentObject)); } - consentData.apiVersion = cmpVersion; - gdprDataHandler.setConsentData(consentData); } /** - * This function handles the exit logic for the module. - * While there are several paths in the module's logic to call this function, we only allow 1 of the 3 potential exits to happen before suppressing others. - * - * We prevent multiple exits to avoid conflicting messages in the console depending on certain scenarios. - * One scenario could be auction was canceled due to timeout with CMP being reached. - * While the timeout is the accepted exit and runs first, the CMP's callback still tries to process the user's data (which normally leads to a good exit). - * In this case, the good exit will be suppressed since we already decided to cancel the auction. - * - * Three exit paths are: - * 1. good exit where auction runs (CMP data is processed normally). - * 2. bad exit but auction still continues (warning message is logged, CMP data is undefined and still passed along). - * 3. bad exit with auction canceled (error message is logged). - * @param {string} errMsg optional; only to be used when there was a 'bad' exit. String is a descriptive message for the failure/issue encountered. - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) - * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging + * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction + * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) */ -function exitModule(errMsg, hookConfig, extraArgs) { - if (hookConfig.haveExited === false) { - hookConfig.haveExited = true; - - let context = hookConfig.context; - let args = hookConfig.args; - let nextFn = hookConfig.nextFn; - - if (errMsg) { - if (allowAuction.value && cmpVersion === 1) { - logWarn(errMsg + ` 'allowAuctionWithoutConsent' activated.`, extraArgs); - nextFn.apply(context, args); - } else { - logError(errMsg + ' Canceling auction as per consentManagement config.', extraArgs); - gdprDataHandler.setConsentData(null); - if (typeof hookConfig.bidsBackHandler === 'function') { - hookConfig.bidsBackHandler(); - } else { - logError('Error executing bidsBackHandler'); - } - } - } else { - nextFn.apply(context, args); - } +function storeConsentData(cmpConsentObject) { + 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 = CMP_VERSION; + return consentData; } /** @@ -470,7 +301,7 @@ function exitModule(errMsg, hookConfig, extraArgs) { export function resetConsentData() { consentData = undefined; userCMP = undefined; - cmpVersion = 0; + consentTimeout = undefined; gdprDataHandler.reset(); } @@ -500,16 +331,10 @@ 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; logInfo('consentManagement module has been activated...'); - gdprDataHandler.enable(); if (userCMP === 'static') { if (isPlainObject(config.consentData)) { @@ -523,5 +348,7 @@ export function setConsentConfig(config) { $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); } addedConsentHook = true; + gdprDataHandler.enable(); + loadConsentData(); // immediately look up consent data to make it available without requiring an auction } config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 75462221403..0224d6ef2c0 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; @@ -27,23 +28,17 @@ const uspCallMap = { /** * This function reads the consent string from the config to obtain the consent information of the user. - * @param {function(string)} cmpSuccess acts as a success callback when the value is read from config; pass along consentObject (string) from CMP - * @param {function(string)} cmpError acts as an error callback while interacting with the config string; pass along an error message (string) - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ -function lookupStaticConsentData(cmpSuccess, cmpError, hookConfig) { - cmpSuccess(staticConsentData, hookConfig); +function lookupStaticConsentData({onSuccess, onError}) { + processUspData(staticConsentData, {onSuccess, onError}); } /** * This function handles interacting with an USP compliant consent manager to obtain the consent information of the user. * Given the async nature of the USP's API, we pass in acting success/error callback functions to exit this function * based on the appropriate result. - * @param {function(string)} uspSuccess acts as a success callback when USPAPI returns a value; pass along consentObject (string) from USPAPI - * @param {function(string)} uspError acts as an error callback while interacting with USPAPI; pass along an error message (string) - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) */ -function lookupUspConsent(uspSuccess, uspError, hookConfig) { +function lookupUspConsent({onSuccess, onError}) { function findUsp() { let f = window; let uspapiFrame; @@ -78,9 +73,9 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { function afterEach() { if (uspResponse.usPrivacy) { - uspSuccess(uspResponse, hookConfig); + processUspData(uspResponse, {onSuccess, onError}) } else { - uspError('Unable to get USP consent string.', hookConfig); + onError('Unable to get USP consent string.'); } } @@ -100,7 +95,7 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { let { uspapiFrame, uspapiFunction } = findUsp(); if (!uspapiFrame) { - return uspError('USP CMP not found.', hookConfig); + return onError('USP CMP not found.'); } // to collect the consent information from the user, we perform a call to USPAPI @@ -165,121 +160,92 @@ function lookupUspConsent(uspSuccess, uspError, hookConfig) { } /** - * If consentManagementUSP module is enabled (ie included in setConfig), this hook function will attempt to fetch the - * user's encoded consent string from the supported USPAPI. Once obtained, the module will store this - * data as part of a uspConsent object which gets transferred to adapterManager's uspDataHandler object. - * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. - * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. - * @param {function} fn required; The next function in the chain, used by hook.js + * Lookup consent data and store it in the `consentData` global as well as `adapterManager.js`' uspDataHanlder. + * + * @param cb a callback that takes an error message and extra error arguments; all args will be undefined if consent + * data was retrieved successfully. */ -export function requestBidsHook(fn, reqBidsConfigObj) { - // preserves all module related variables for the current auction instance (used primiarily for concurrent auctions) - const hookConfig = { - context: this, - args: [reqBidsConfigObj], - nextFn: fn, - adUnits: reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits, - bidsBackHandler: reqBidsConfigObj.bidsBackHandler, - haveExited: false, - timer: null - }; +function loadConsentData(cb) { + let timer = null; + let isDone = false; + + function done(consentData, errMsg, ...extraArgs) { + if (timer != null) { + clearTimeout(timer); + } + isDone = true; + uspDataHandler.setConsentData(consentData); + if (cb != null) { + cb(errMsg, ...extraArgs) + } + } if (!uspCallMap[consentAPI]) { - logWarn(`USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`); - uspDataHandler.setConsentData(null); - return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); + done(null, `USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`); + return; } - uspCallMap[consentAPI].call(this, processUspData, uspapiFailed, hookConfig); + const callbacks = { + onSuccess: done, + onError: function (errMsg, ...extraArgs) { + done(null, `${errMsg} Resuming auction without consent data as per consentManagement config.`, ...extraArgs); + } + } - // only let this code run if module is still active (ie if the callbacks used by USPs haven't already finished) - if (!hookConfig.haveExited) { + uspCallMap[consentAPI](callbacks); + + if (!isDone) { if (consentTimeout === 0) { - processUspData(undefined, hookConfig); + processUspData(undefined, callbacks); } else { - hookConfig.timer = setTimeout(uspapiTimeout.bind(null, hookConfig), consentTimeout); + timer = setTimeout(callbacks.onError.bind(null, 'USPAPI workflow exceeded timeout threshold.'), consentTimeout) } } } +/** + * If consentManagementUSP module is enabled (ie included in setConfig), this hook function will attempt to fetch the + * user's encoded consent string from the supported USPAPI. Once obtained, the module will store this + * data as part of a uspConsent object which gets transferred to adapterManager's uspDataHandler object. + * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. + * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * @param {function} fn required; The next function in the chain, used by hook.js + */ +export function requestBidsHook(fn, reqBidsConfigObj) { + loadConsentData((errMsg, ...extraArgs) => { + if (errMsg != null) { + logWarn(errMsg, ...extraArgs); + } + fn.call(this, reqBidsConfigObj); + }); +} + /** * This function checks the consent data provided by USPAPI to ensure it's in an expected state. * If it's bad, we exit the module depending on config settings. * If it's good, then we store the value and exits the module. * @param {object} consentObject required; object returned by USPAPI that contains user's consent choices - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + * @param {function(string)} onSuccess callback accepting the resolved consent USP consent string + * @param {function(string, ...{}?)} onError callback accepting error message and any extra error arguments (used purely for logging) */ -function processUspData(consentObject, hookConfig) { +function processUspData(consentObject, {onSuccess, onError}) { const valid = !!(consentObject && consentObject.usPrivacy); if (!valid) { - uspapiFailed(`USPAPI returned unexpected value during lookup process.`, hookConfig, consentObject); + onError(`USPAPI returned unexpected value during lookup process.`, consentObject); return; } - clearTimeout(hookConfig.timer); storeUspConsentData(consentObject); - exitModule(null, hookConfig); -} - -/** - * General timeout callback when interacting with USPAPI takes too long. - */ -function uspapiTimeout(hookConfig) { - uspapiFailed('USPAPI workflow exceeded timeout threshold.', hookConfig); -} - -/** - * This function contains the controlled steps to perform when there's a problem with USPAPI. - * @param {string} errMsg required; should be a short descriptive message for why the failure/issue happened. - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) - * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging -*/ -function uspapiFailed(errMsg, hookConfig, extraArgs) { - clearTimeout(hookConfig.timer); - - exitModule(errMsg, hookConfig, extraArgs); + onSuccess(consentData); } /** * Stores USP data locally in module and then invokes uspDataHandler.setConsentData() to make information available in adaptermanger.js for later in the auction - * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) + * @param {object} consentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) */ function storeUspConsentData(consentObject) { if (consentObject && consentObject.usPrivacy) { consentData = consentObject.usPrivacy; - uspDataHandler.setConsentData(consentData); - } -} - -/** - * This function handles the exit logic for the module. - * There are a couple paths in the module's logic to call this function and we only allow 1 of the 2 potential exits to happen before suppressing others. - * - * We prevent multiple exits to avoid conflicting messages in the console depending on certain scenarios. - * One scenario could be auction was canceled due to timeout with USPAPI being reached. - * While the timeout is the accepted exit and runs first, the USP's callback still tries to process the user's data (which normally leads to a good exit). - * In this case, the good exit will be suppressed since we already decided to cancel the auction. - * - * Three exit paths are: - * 1. good exit where auction runs (USPAPI data is processed normally). - * 2. bad exit but auction still continues (warning message is logged, USPAPI data is undefined and still passed along). - * @param {string} errMsg optional; only to be used when there was a 'bad' exit. String is a descriptive message for the failure/issue encountered. - * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) - * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging - */ -function exitModule(errMsg, hookConfig, extraArgs) { - if (hookConfig.haveExited === false) { - hookConfig.haveExited = true; - - let context = hookConfig.context; - let args = hookConfig.args; - let nextFn = hookConfig.nextFn; - - if (errMsg) { - logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.', extraArgs); - uspDataHandler.setConsentData(null) // let core know that no consent data is available - } - nextFn.apply(context, args); } } @@ -289,7 +255,10 @@ function exitModule(errMsg, hookConfig, extraArgs) { export function resetConsentData() { consentData = undefined; consentAPI = undefined; + consentTimeout = undefined; uspDataHandler.reset(); + getGlobal().requestBids.getHooks({hook: requestBidsHook}).remove(); + addedConsentHook = false; } /** @@ -299,26 +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...'); - uspDataHandler.enable(); - if (consentAPI === 'static') { if (isPlainObject(config.consentData) && isPlainObject(config.consentData.getUSPData)) { if (config.consentData.getUSPData.uspString) staticConsentData = { usPrivacy: config.consentData.getUSPData.uspString }; @@ -327,9 +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 1a2845ba85b..9a5f5f4675b 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -1,4 +1,5 @@ -import { logWarn, createTrackPixelHtml } from '../src/utils.js'; +import { logWarn, createTrackPixelHtml, deepAccess, isArray, deepSetValue } from '../src/utils.js'; +import {config} from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'consumable'; @@ -47,8 +48,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 +67,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({ @@ -78,6 +87,8 @@ export const spec = { } }); + handleEids(data, validBidRequests); + ret.data = JSON.stringify(data); ret.bidRequest = validBidRequests; ret.bidderRequest = bidderRequest; @@ -122,9 +133,29 @@ export const spec = { bid.currency = 'USD'; bid.creativeId = decision.adId; bid.ttl = 30; - bid.meta = { advertiserDomains: decision.adomain ? decision.adomain : [] } bid.netRevenue = true; - bid.referrer = bidRequest.bidderRequest.refererInfo.referer; + bid.referrer = bidRequest.bidderRequest.refererInfo.page; + + bid.meta = { + advertiserDomains: decision.adomain || [] + }; + + if (decision.cats) { + if (decision.cats.length > 0) { + bid.meta.primaryCatId = decision.cats[0]; + if (decision.cats.length > 1) { + bid.meta.secondaryCatIds = decision.cats.slice(1); + } + } + } + + if (decision.networkId) { + bid.meta.networkId = decision.networkId; + } + + if (decision.mediaType) { + bid.meta.mediaType = decision.mediaType; + } bidResponses.push(bid); } @@ -136,13 +167,15 @@ export const spec = { getUserSyncs: function(syncOptions, serverResponses) { if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: 'https://sync.serverbid.com/ss/' + siteId + '.html' - }]; + if (!serverResponses || serverResponses.length === 0 || !serverResponses[0].body.bdr || serverResponses[0].body.bdr !== 'cx') { + return [{ + type: 'iframe', + url: 'https://sync.serverbid.com/ss/' + siteId + '.html' + }]; + } } - if (syncOptions.pixelEnabled && serverResponses.length > 0) { + if (syncOptions.pixelEnabled && serverResponses && serverResponses.length > 0) { return serverResponses[0].body.pixels; } else { logWarn(bidder + ': Please enable iframe based user syncing.'); @@ -212,4 +245,13 @@ function retrieveAd(decision, unitId, unitName) { 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); + } +} + registerBidder(spec); diff --git a/modules/contentexchangeBidAdapter.js b/modules/contentexchangeBidAdapter.js index b3a5056f816..906359252ee 100644 --- a/modules/contentexchangeBidAdapter.js +++ b/modules/contentexchangeBidAdapter.js @@ -127,7 +127,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 +135,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 7ee8b1b7681..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; @@ -93,7 +92,7 @@ export const spec = { copyOptProperty(format[0].h, video, 'h'); } - copyOptProperty(bid.params.position, video, 'pos'); + copyOptProperty(bid.params.position || videoData.pos, video, 'pos'); copyOptProperty(bid.params.mimes || videoData.mimes, video, 'mimes'); copyOptProperty(bid.params.maxduration || videoData.maxduration, video, 'maxduration'); copyOptProperty(bid.params.protocols || videoData.protocols, video, 'protocols'); @@ -105,7 +104,7 @@ export const spec = { const format = convertSizes(bannerData.sizes || bid.sizes); const banner = {format: format}; - copyOptProperty(bid.params.position, banner, 'pos'); + copyOptProperty(bid.params.position || bannerData.pos, banner, 'pos'); imp.banner = banner; } @@ -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 new file mode 100644 index 00000000000..5c2e6bc212d --- /dev/null +++ b/modules/cpexIdSystem.js @@ -0,0 +1,47 @@ +/** + * This module adds 'caid' to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/cpexIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js' +import { getStorageManager } from '../src/storageManager.js' + +// Returns StorageManager +export const storage = getStorageManager({ gvlid: 570, moduleName: 'cpexId' }) + +// Returns the id string from either cookie or localstorage +const readId = () => { return storage.getCookie('czaid') || storage.getDataFromLocalStorage('czaid') } + +/** @type {Submodule} */ +export const cpexIdSubmodule = { + version: '0.0.5', + /** + * used to link submodule with config + * @type {string} + */ + name: 'cpexId', + /** + * Vendor ID of Czech Publisher Exchange + * @type {Number} + */ + gvlid: 570, + /** + * decode the stored id value for passing to bid requests + * @function decode + * @returns {(Object|undefined)} + */ + decode () { return { cpexId: readId() } }, + /** + * performs action to obtain id and return a value in the callback's response argument + * @function + * @returns {IdResponse|undefined} + */ + getId () { + const id = readId() + return id ? { id: id } : undefined + } +} + +submodule('userId', cpexIdSubmodule) diff --git a/modules/cpexIdSystem.md b/modules/cpexIdSystem.md new file mode 100644 index 00000000000..8aceb7fe4ec --- /dev/null +++ b/modules/cpexIdSystem.md @@ -0,0 +1,27 @@ +## CPEx User ID Submodule + +CPExID is provided by [Czech Publisher Exchange](https://www.cpex.cz/), or CPEx. It is a user ID for ad targeting by using first party cookie, or localStorage mechanism. Please contact CPEx before using this ID. + +## Building Prebid with CPExID Support + +First, make sure to add the cpexId to your Prebid.js package with: + +``` +gulp build --modules=cpexIdSystem +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'cpexId' + }] + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `"cpexId"` | diff --git a/modules/cpmstarBidAdapter.js b/modules/cpmstarBidAdapter.js index 75a7007ee36..6e32c5c4713 100755 --- a/modules/cpmstarBidAdapter.js +++ b/modules/cpmstarBidAdapter.js @@ -46,7 +46,8 @@ export const spec = { for (var i = 0; i < validBidRequests.length; i++) { var bidRequest = validBidRequests[i]; - var referer = encodeURIComponent(bidderRequest.refererInfo.referer); + // TODO: is 'page' the right value here? + var referer = encodeURIComponent(bidderRequest.refererInfo.page); var e = getBidIdParameter('endpoint', bidRequest.params); var ENDPOINT = e == 'dev' ? ENDPOINT_DEV : e == 'staging' ? ENDPOINT_STAGING : ENDPOINT_PRODUCTION; var mediaType = spec.getMediaType(bidRequest); diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 61ca4f929e7..147e45d3d65 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -1,7 +1,6 @@ import { convertCamelToUnderscore, convertTypes, - deepAccess, getBidRequest, isArray, isEmpty, @@ -14,6 +13,7 @@ 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'; const BIDDER_CODE = 'craft'; const URL_BASE = 'https://gacraft.jp/prebid-v3'; @@ -51,7 +51,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 +137,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 75d41d970a9..70c4e15da04 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,11 +1,11 @@ -import {deepAccess, getUniqueIdentifierStr, 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'; -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 { deepAccess, getUniqueIdentifierStr, 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'; +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'; const GVLID = 91; export const ADAPTER_VERSION = 34; @@ -13,7 +13,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: '; /* @@ -24,7 +24,7 @@ const LOG_PREFIX = 'Criteo: '; Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js */ const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 117; +export const FAST_BID_VERSION_CURRENT = 123; const FAST_BID_VERSION_LATEST = 'latest'; const FAST_BID_VERSION_NONE = 'none'; const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; @@ -35,7 +35,7 @@ const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDe export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [ BANNER, VIDEO, NATIVE ], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** f * @param {object} bid @@ -65,11 +65,11 @@ export const spec = { buildRequests: (bidRequests, bidderRequest) => { let url; let data; - let fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + let fpd = bidderRequest.ortb2 || {}; Object.assign(bidderRequest, { - publisherExt: fpd.context, - userExt: fpd.user, + publisherExt: fpd.site?.ext, + userExt: fpd.user?.ext, ceh: config.getConfig('criteo.ceh') }); @@ -139,6 +139,13 @@ export const spec = { height: slot.height, dealId: slot.dealCode, }; + if (body.ext?.paf?.transmission && slot.ext?.paf?.content_id) { + const pafResponseMeta = { + content_id: slot.ext.paf.content_id, + transmission: response.ext.paf.transmission + }; + bid.meta = Object.assign({}, bid.meta, { paf: pafResponseMeta }); + } if (slot.adomain) { bid.meta = Object.assign({}, bid.meta, { advertiserDomains: slot.adomain }); } @@ -217,9 +224,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, @@ -248,6 +255,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,11 +278,11 @@ function checkNativeSendId(bidRequest) { return !(bidRequest.nativeParams && ( (bidRequest.nativeParams.image && ((bidRequest.nativeParams.image.sendId !== true || bidRequest.nativeParams.image.sendTargetingKeys === true))) || - (bidRequest.nativeParams.icon && ((bidRequest.nativeParams.icon.sendId !== true || bidRequest.nativeParams.icon.sendTargetingKeys === true))) || - (bidRequest.nativeParams.clickUrl && ((bidRequest.nativeParams.clickUrl.sendId !== true || bidRequest.nativeParams.clickUrl.sendTargetingKeys === true))) || - (bidRequest.nativeParams.displayUrl && ((bidRequest.nativeParams.displayUrl.sendId !== true || bidRequest.nativeParams.displayUrl.sendTargetingKeys === true))) || - (bidRequest.nativeParams.privacyLink && ((bidRequest.nativeParams.privacyLink.sendId !== true || bidRequest.nativeParams.privacyLink.sendTargetingKeys === true))) || - (bidRequest.nativeParams.privacyIcon && ((bidRequest.nativeParams.privacyIcon.sendId !== true || bidRequest.nativeParams.privacyIcon.sendTargetingKeys === true))) + (bidRequest.nativeParams.icon && ((bidRequest.nativeParams.icon.sendId !== true || bidRequest.nativeParams.icon.sendTargetingKeys === true))) || + (bidRequest.nativeParams.clickUrl && ((bidRequest.nativeParams.clickUrl.sendId !== true || bidRequest.nativeParams.clickUrl.sendTargetingKeys === true))) || + (bidRequest.nativeParams.displayUrl && ((bidRequest.nativeParams.displayUrl.sendId !== true || bidRequest.nativeParams.displayUrl.sendTargetingKeys === true))) || + (bidRequest.nativeParams.privacyLink && ((bidRequest.nativeParams.privacyLink.sendId !== true || bidRequest.nativeParams.privacyLink.sendTargetingKeys === true))) || + (bidRequest.nativeParams.privacyIcon && ((bidRequest.nativeParams.privacyIcon.sendId !== true || bidRequest.nativeParams.privacyIcon.sendTargetingKeys === true))) )); } @@ -285,7 +298,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { const request = { publisher: { url: context.url, - ext: bidderRequest.publisherExt + ext: bidderRequest.publisherExt, }, slots: bidRequests.map(bidRequest => { networkId = bidRequest.params.networkId || networkId; @@ -405,7 +418,7 @@ function hasValidVideoMediaType(bidRequest) { var requiredMediaTypesParams = ['mimes', 'playerSize', 'maxduration', 'protocols', 'api', 'skip', 'placement', 'playbackmethod']; - requiredMediaTypesParams.forEach(function(param) { + requiredMediaTypesParams.forEach(function (param) { if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) { isValid = false; logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required'); 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 a59a9880af1..392817b5822 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -5,6 +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 {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; @@ -21,22 +22,12 @@ var bidderCurrencyDefault = {}; var defaultRates; export const ready = (() => { - let isDone, resolver, promise; + let ctl; function reset() { - isDone = false; - resolver = null; - promise = new Promise((resolve) => { - resolver = resolve; - if (isDone) resolve(); - }) - } - function done() { - isDone = true; - if (resolver != null) { resolver() } + ctl = defer(); } reset(); - - return {done, reset, promise: () => promise} + return {done: () => ctl.resolve(), reset, promise: () => ctl.promise} })(); /** @@ -168,6 +159,8 @@ function initCurrency(url) { } } ); + } else { + ready.done(); } } diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index c0a24b49a3c..47f3020fec3 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 || '', 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/dacIdSystem.md b/modules/dacIdSystem.md index b422d0a536d..0239b4557e9 100644 --- a/modules/dacIdSystem.md +++ b/modules/dacIdSystem.md @@ -1,11 +1,11 @@ -## DAC User ID Submodule +## AudienceOne User ID Submodule -DAC ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie. +AudienceOne ID, provided by [D.A.Consortium Inc.](https://www.dac.co.jp/), is ID for ad targeting by using 1st party cookie. Please contact D.A.Consortium Inc. before using this ID. -## Building Prebid with DAC ID Support +## Building Prebid with AudienceOne ID Support -First, make sure to add the DAC ID submodule to your Prebid.js package with: +First, make sure to add the AudienceOne ID submodule to your Prebid.js package with: ``` gulp build --modules=dacIdSystem diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js index ffa84ff88fd..590f3e79f85 100644 --- a/modules/dailyhuntBidAdapter.js +++ b/modules/dailyhuntBidAdapter.js @@ -96,7 +96,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: [], 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/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index b240db1dd25..fb036d4ff0c 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -347,10 +347,11 @@ 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, @@ -383,7 +384,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/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 5af2384cad9..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 * 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 2dfd9ea4386..37f038d2a67 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -11,6 +11,7 @@ import { auctionManager } from '../src/auctionManager.js'; import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; +import {getPPID} from '../src/adserver.js'; /** * @typedef {Object} DfpVideoParams @@ -88,7 +89,14 @@ export function buildDfpVideoUrl(options) { sz: parseSizesInput(deepAccess(adUnit, 'mediaTypes.video.playerSize')).join('|'), url: encodeURIComponent(location.href), }; - const encodedCustomParams = getCustParams(bid, options); + + const urlSearchComponent = urlComponents.search; + const urlSzParam = urlSearchComponent && urlSearchComponent.sz + if (urlSzParam) { + derivedParams.sz = urlSzParam + '|' + derivedParams.sz; + } + + let encodedCustomParams = getCustParams(bid, options, urlSearchComponent && urlSearchComponent.cust_params); const queryParams = Object.assign({}, defaultParamConstants, @@ -111,12 +119,18 @@ export function buildDfpVideoUrl(options) { const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } - return buildUrl({ + if (!queryParams.ppid) { + const ppid = getPPID(); + if (ppid != null) { + queryParams.ppid = ppid; + } + } + + return buildUrl(Object.assign({ protocol: 'https', host: 'securepubads.g.doubleclick.net', - pathname: '/gampad/ads', - search: queryParams - }); + pathname: '/gampad/ads' + }, urlComponents, { search: queryParams })); } export function notifyTranslationModule(fn) { @@ -227,9 +241,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) { const descriptionUrl = getDescriptionUrl(bid, components, 'search'); if (descriptionUrl) { components.search.description_url = descriptionUrl; } - const encodedCustomParams = getCustParams(bid, options); - components.search.cust_params = (components.search.cust_params) ? components.search.cust_params + '%26' + encodedCustomParams : encodedCustomParams; - + components.search.cust_params = getCustParams(bid, options, components.search.cust_params); return buildUrl(components); } @@ -258,7 +270,7 @@ function getDescriptionUrl(bid, components, prop) { * @param {Object} options this is the options passed in from the `buildDfpVideoUrl` function * @return {Object} Encoded key value pairs for cust_params */ -function getCustParams(bid, options) { +function getCustParams(bid, options, urlCustParams) { const adserverTargeting = (bid && bid.adserverTargeting) || {}; let allTargetingData = {}; @@ -281,7 +293,12 @@ function getCustParams(bid, options) { // merge the prebid + publisher targeting sets const publisherTargetingSet = deepAccess(options, 'params.cust_params'); const targetingSet = Object.assign({}, prebidTargetingSet, publisherTargetingSet); - return encodeURIComponent(formatQS(targetingSet)); + let encodedParams = encodeURIComponent(formatQS(targetingSet)); + if (urlCustParams) { + encodedParams = urlCustParams + '%26' + encodedParams; + } + + return encodedParams; } registerVideoSupport('dfp', { 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..171766ac4ce 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'; @@ -62,8 +62,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..ae6ae8f6f03 --- /dev/null +++ b/modules/dianomiBidAdapter.js @@ -0,0 +1,369 @@ +// 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'; + +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) => { + 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/displayioBidAdapter.js b/modules/displayioBidAdapter.js index 55a2f4a8604..e039d461fc7 100644 --- a/modules/displayioBidAdapter.js +++ b/modules/displayioBidAdapter.js @@ -88,9 +88,10 @@ export const spec = { 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, + // TODO: are these the correct refererInfo values? + domain: refererInfo.domain, + page: refererInfo.page, + ref: refererInfo.ref, userids: _getUserIDs(), geo: '', }, 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 new file mode 100644 index 00000000000..005dd3e67d6 --- /dev/null +++ b/modules/distroscaleBidAdapter.js @@ -0,0 +1,263 @@ +import { logWarn, isPlainObject, isStr, isArray, isFn, inIframe, mergeDeep, deepSetValue, logError, deepClone } 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 = 'distroscale'; +const SHORT_CODE = 'ds'; +const LOG_WARN_PREFIX = 'DistroScale: '; +const ENDPOINT = 'https://hb.jsrdn.com/hb?from=pbjs'; +const DEFAULT_CURRENCY = 'USD'; +const AUCTION_TYPE = 1; +const GVLID = 754; +const UNDEF = undefined; + +const SUPPORTED_MEDIATYPES = [ BANNER ]; + +function _getHost(url) { + let a = document.createElement('a'); + a.href = url; + return a.hostname; +} + +function _getBidFloor(bid, mType, sz) { + if (isFn(bid.getFloor)) { + let floor = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: mType || '*', + size: sz || '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) { + return floor.floor; + } + } + return null; +} + +function _createImpressionObject(bid) { + var impObj = UNDEF; + var i; + var sizes = {}; + var sizesCount = 0; + + function addSize(arr) { + var w, h; + if (arr && arr.length > 1) { + w = parseInt(arr[0]); + h = parseInt(arr[1]); + } + sizes[w + 'x' + h] = { + w: w, + h: h, + area: w * h, + idx: + ({ + '970x250': 1, + '300x250': 2 + })[w + 'x' + h] || Math.max(w * h, 200) + }; + sizesCount++; + } + + // Gather all sizes + if (isArray(bid.sizes)) { + for (i = 0; i < bid.sizes.length; i++) { + addSize(bid.sizes[i]); + } + } + if (bid.params && bid.params.width && bid.params.height) { + addSize([bid.params.width, bid.params.height]); + } + if (bid.mediaTypes && BANNER in bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { + for (i = 0; i < bid.mediaTypes[BANNER].sizes.length; i++) { + addSize(bid.mediaTypes[BANNER].sizes[i]); + } + } + if (sizesCount == 0) { + logWarn(LOG_WARN_PREFIX + 'Error: missing sizes: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); + } else { + // Use the first preferred size + var keys = Object.keys(sizes); + keys.sort(function(a, b) { + return sizes[a].idx - sizes[b].idx + }); + var bannerObj = { + pos: 0, + w: sizes[keys[0]].w, + h: sizes[keys[0]].h, + topframe: inIframe() ? 0 : 1, + format: [{ + 'w': sizes[keys[0]].w, + 'h': sizes[keys[0]].h + }] + }; + + impObj = { + id: bid.bidId, + tagid: bid.params.zoneid || '', + secure: 1, + ext: { + pubid: bid.params.pubid || '', + zoneid: bid.params.zoneid || '' + } + }; + + var floor = _getBidFloor(bid, BANNER, [sizes[keys[0]].w, sizes[keys[0]].h]); + if (floor > 0) { + impObj.bidfloor = floor; + impObj.bidfloorcur = DEFAULT_CURRENCY; + } + + impObj[BANNER] = bannerObj; + } + + return impObj; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: SUPPORTED_MEDIATYPES, + aliases: [SHORT_CODE], + + isBidRequestValid: bid => { + if (bid && bid.params && bid.params.pubid && isStr(bid.params.pubid)) { + return true; + } else { + logWarn(LOG_WARN_PREFIX + 'Error: pubid is mandatory and cannot be numeric'); + } + return false; + }, + + buildRequests: (validBidRequests, bidderRequest) => { + // TODO: does the fallback to window.location make sense? + var pageUrl = bidderRequest?.refererInfo?.page || window.location.href; + + var payload = { + id: '' + (new Date()).getTime(), + at: AUCTION_TYPE, + cur: [DEFAULT_CURRENCY], + site: { + page: pageUrl + }, + device: { + ua: navigator.userAgent, + js: 1, + h: screen.height, + w: screen.width, + language: (navigator.language && navigator.language.replace(/-.*/, '')) || 'en', + dnt: (navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1' || navigator.doNotTrack == 'yes') ? 1 : 0 + }, + imp: [], + user: {}, + ext: {} + }; + + validBidRequests.forEach(b => { + var bid = deepClone(b); + var impObj = _createImpressionObject(bid); + if (impObj) { + payload.imp.push(impObj); + } + }); + + if (payload.imp.length == 0) { + return; + } + + payload.site.domain = _getHost(payload.site.page); + + // add the content object from config in request + if (typeof config.getConfig('content') === 'object') { + payload.site.content = config.getConfig('content'); + } + + // merge the device from config.getConfig('device') + if (typeof config.getConfig('device') === 'object') { + payload.device = Object.assign(payload.device, config.getConfig('device')); + } + + // adding schain object + if (validBidRequests[0].schain) { + deepSetValue(payload, 'source.schain', validBidRequests[0].schain); + } + + // Attaching GDPR Consent Params + if (bidderRequest && bidderRequest.gdprConsent) { + deepSetValue(payload, 'user.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + deepSetValue(payload, 'regs.us_privacy', bidderRequest.uspConsent); + } + + // coppa compliance + if (config.getConfig('coppa') === true) { + deepSetValue(payload, 'regs.coppa', 1); + } + + // First Party Data + const commonFpd = bidderRequest.ortb2 || {}; + if (commonFpd.site) { + mergeDeep(payload, {site: commonFpd.site}); + } + if (commonFpd.user) { + mergeDeep(payload, {user: commonFpd.user}); + } + + // User IDs + if (validBidRequests[0].userIdAsEids && validBidRequests[0].userIdAsEids.length > 0) { + // Standard ORTB structure + deepSetValue(payload, 'user.eids', validBidRequests[0].userIdAsEids); + } else if (validBidRequests[0].userId && Object.keys(validBidRequests[0].userId).length > 0) { + // Fallback to non-ortb structure + deepSetValue(payload, 'user.ext.userId', validBidRequests[0].userId); + } + + return { + method: 'POST', + url: ENDPOINT, + data: payload, + bidderRequest: bidderRequest + }; + }, + + interpretResponse: (response, request) => { + const bidResponses = []; + try { + if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { + // Supporting multiple bid responses for same adSize + response.body.seatbid.forEach(seatbidder => { + seatbidder.bid && + isArray(seatbidder.bid) && + seatbidder.bid.forEach(bid => { + let newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0), + currency: DEFAULT_CURRENCY, + width: parseInt(bid.w), + height: parseInt(bid.h), + creativeId: bid.crid || bid.id, + netRevenue: true, + ttl: 300, + ad: bid.adm, + meta: { + advertiserDomains: [] + } + }; + if (isArray(bid.adomain) && bid.adomain.length > 0) { + newBid.meta.advertiserDomains = bid.adomain; + } + bidResponses.push(newBid); + }); + }); + } + } catch (error) { + logError(error); + } + return bidResponses; + } +}; + +registerBidder(spec); diff --git a/modules/distroscaleBidAdapter.md b/modules/distroscaleBidAdapter.md new file mode 100644 index 00000000000..1d7948b2a02 --- /dev/null +++ b/modules/distroscaleBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: DistroScale Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@distroscale.com +``` + +# Description + +Connects to DistroScale exchange for bids. DistroScale bid adapter supports Banner currently. + +# Test Parameters +``` +var adUnits = [{ + code: 'banner-1', + mediaTypes: { + banner: { + sizes: [[970, 250]], + } + }, + bids: [{ + bidder: 'distroscale', + params: { + pubid: '12345' // required, must be a string + ,zoneid: '67890' // optional, must be a string + } + }] +}]; +``` 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..6af0236d3bb 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; diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 884c4f0c067..a4372fc5dec 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -41,6 +41,19 @@ function getBidFloor(bid) { } } +function getUserId(eids, id, source, uidExt) { + if (id) { + var uid = { id }; + if (uidExt) { + uid.ext = uidExt; + } + eids.push({ + source, + uids: [ uid ] + }); + } +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -53,8 +66,9 @@ export const spec = { buildRequests: (validBidRequests = [], bidderRequest) => { 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; @@ -86,7 +100,12 @@ export const spec = { const placement = { placementId: bid.params.placementId, bidId: bid.bidId, - bidfloor: getBidFloor(bid) + bidfloor: getBidFloor(bid), + eids: [] + } + + if (bid.userId) { + getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com'); } if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { 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..d904ebda9c8 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) => { diff --git a/modules/engageyaBidAdapter.js b/modules/engageyaBidAdapter.js index adf26c38dd5..ceec1de8fe7 100644 --- a/modules/engageyaBidAdapter.js +++ b/modules/engageyaBidAdapter.js @@ -1,9 +1,6 @@ import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { createTrackPixelHtml } from '../src/utils.js'; - -const { - registerBidder -} = require('../src/adapters/bidderFactory.js'); +import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'engageya'; const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json'; const ENDPOINT_METHOD = 'GET'; @@ -16,9 +13,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; diff --git a/modules/enrichmentFpdModule.js b/modules/enrichmentFpdModule.js index 9268c81c033..139b03d6189 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) } } }); @@ -151,17 +142,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 fb77014400c..365f91382f7 100644 --- a/modules/eplanningAnalyticsAdapter.js +++ b/modules/eplanningAnalyticsAdapter.js @@ -2,8 +2,7 @@ import { logError } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; - -const CONSTANTS = require('../src/constants.json'); +import CONSTANTS from '../src/constants.json'; const analyticsType = 'endpoint'; const EPL_HOST = 'https://ads.us.e-planning.net/hba/1/'; diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 780531964ad..a6adab1e9b9 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); @@ -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..24eac8517b0 100644 --- a/modules/fabrickIdSystem.js +++ b/modules/fabrickIdSystem.js @@ -73,7 +73,7 @@ export const fabrickIdSubmodule = { 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]); } 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..c4bb2dffe28 --- /dev/null +++ b/modules/finativeBidAdapter.js @@ -0,0 +1,235 @@ +// 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'; + +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) => { + 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 12abd2d0efd..7ecc7e963b5 100644 --- a/modules/fintezaAnalyticsAdapter.js +++ b/modules/fintezaAnalyticsAdapter.js @@ -3,9 +3,9 @@ import { ajax } from '../src/ajax.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; +import CONSTANTS from '../src/constants.json'; const storage = getStorageManager(); -const CONSTANTS = require('../src/constants.json'); const ANALYTICS_TYPE = 'endpoint'; const FINTEZA_HOST = 'https://content.mql5.com/tr'; diff --git a/modules/flocIdSystem.js b/modules/flocIdSystem.js index 0cff7e86d73..e69de29bb2d 100644 --- a/modules/flocIdSystem.js +++ b/modules/flocIdSystem.js @@ -1,110 +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) { - document.interestCohort() - .then((data) => { - successCallback(data); - }).catch((error) => { - errorCallback(error); - }); -} - -/** - * 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 = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime) && !!document.featurePolicy && !!document.featurePolicy.features() && document.featurePolicy.features().includes('interest-cohort'); - - 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..bd735e215a4 100644 --- a/modules/fpdModule/index.js +++ b/modules/fpdModule/index.js @@ -4,55 +4,39 @@ */ 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'; let submodules = []; -/** - * enable submodule in User ID - * @param {RtdSubmodule} submodule - */ export function registerSubmodules(submodule) { submodules.push(submodule); } -export function init() { +export function reset() { + submodules.length = 0; +} + +export function processFpd({global = {}, bidder = {}} = {}) { let modConf = config.getConfig('firstPartyData') || {}; - let ortb2 = config.getConfig('ortb2') || {}; submodules.sort((a, b) => { return ((a.queue || 1) - (b.queue || 1)); }).forEach(submodule => { - ortb2 = submodule.init(modConf, ortb2); + ({global = global, bidder = bidder} = submodule.processFpd(modConf, {global, bidder})); }); - config.setConfig({ortb2}); + return {global, bidder}; } -/** - * 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) { + Object.assign(req.ortb2Fragments, processFpd(req.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..91453fdcf5d 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -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 c570e69e1d3..55f04e6b1d3 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -9,6 +9,7 @@ import * as utils from '../src/utils.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; import { uspDataHandler } from '../src/adapterManager.js'; +import { loadExternalScript } from '../src/adloader.js'; const MODULE_NAME = 'ftrackId'; const LOG_PREFIX = 'FTRACK - '; @@ -17,7 +18,6 @@ const VENDOR_ID = null; const LOCAL_STORAGE = 'html5'; const FTRACK_STORAGE_NAME = 'ftrackId'; const FTRACK_PRIVACY_STORAGE_NAME = `${FTRACK_STORAGE_NAME}_privacy`; -const FTRACK_URL = 'https://d9.flashtalking.com/d9core'; const storage = getStorageManager({gvlid: VENDOR_ID, moduleName: MODULE_NAME}); let consentInfo = { @@ -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,21 +71,19 @@ 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', CCampID: '148556' }; window.D9r = { - DeviceID: true, - SingleDeviceID: true, callback: function(response) { if (response) { storage.setDataInLocalStorage(`${FTRACK_STORAGE_NAME}_exp`, (new Date(Date.now() + (1000 * 60 * 60 * 24 * LOCAL_STORAGE_EXP_DAYS))).toUTCString()); @@ -84,15 +93,30 @@ export const ftrackIdSubmodule = { storage.setDataInLocalStorage(`${FTRACK_PRIVACY_STORAGE_NAME}`, JSON.stringify(consentInfo)); }; + if (typeof cb === 'function') cb(response); + return response; } }; - if (config.params && config.params.url && config.params.url === FTRACK_URL) { - var ftrackScript = document.createElement('script'); - ftrackScript.setAttribute('src', config.params.url); - window.document.body.appendChild(ftrackScript); + // If config.params.ids does not exist, set defaults + if (!config.params.hasOwnProperty('ids')) { + window.D9r.DeviceID = true; + window.D9r.SingleDeviceID = true; + } else { + if (config.params.ids.hasOwnProperty('device id') && config.params.ids['device id'] === true) { + window.D9r.DeviceID = true; + } + if (config.params.ids.hasOwnProperty('single device id') && config.params.ids['single device id'] === true) { + window.D9r.SingleDeviceID = true; + } + if (config.params.ids.hasOwnProperty('household id') && config.params.ids['household id'] === true) { + window.D9r.HHID = true; + } } + + // Creates an async script element and appends it to the document + loadExternalScript(config.params.url, MODULE_NAME); } }; }, @@ -132,8 +156,8 @@ export const ftrackIdSubmodule = { utils.logWarn(LOG_PREFIX + 'config.storage.name recommended to be "' + FTRACK_STORAGE_NAME + '".'); } - if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url') || config.params.url !== FTRACK_URL) { - utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run. Url should be "' + FTRACK_URL + '".'); + if (!config.hasOwnProperty('params') || !config.params.hasOwnProperty('url')) { + utils.logWarn(LOG_PREFIX + 'config.params.url is required for ftrack to run.'); return false; } diff --git a/modules/ftrackIdSystem.md b/modules/ftrackIdSystem.md index c5f255c2fc2..24a8dbd08b6 100644 --- a/modules/ftrackIdSystem.md +++ b/modules/ftrackIdSystem.md @@ -30,7 +30,12 @@ pbjs.setConfig({ userIds: [{ name: 'FTrack', params: { - url: 'https://d9.flashtalking.com/d9core' // required, if not populated ftrack will not run + url: 'https://d9.flashtalking.com/d9core', // required, if not populated ftrack will not run + ids: { + 'device id': true, + 'single device id': true, + 'household id': true + } }, storage: { type: 'html5', // "html5" is the required storage type @@ -47,6 +52,12 @@ pbjs.setConfig({ | Param under userSync.userIds[] | Scope | Type | Description | Example | | :-- | :-- | :-- | :-- | :-- | | name | Required | String | The name of this module: `"FTrack"` | `"FTrack"` | +| params | Required | Object | The IDs available, if not populated then the defaults "Device ID" and "Single Device ID" will be returned | | +| params.url | Required | String | The URL for the ftrack library reference. If not populate, ftrack will not run. | 'https://d9.flashtalking.com/d9core' | +| params.ids | Optional | Object | The ftrack IDs available, if not populated then the defaults "Device ID" and "Single Device ID" will be returned | | +| params.ids['device id'] | Optional | Boolean | Should ftrack return "device id". Set to `true` to return it. If set to `undefined` or `false`, ftrack will not return "device id". Default is `false` | `true` | +| params.ids['single device id'] | Optional | Boolean | Should ftrack return "single device id". Set to `true` to return it. If set to `undefined` or `false`, ftrack will not return "single device id". Default is `false` | `true` | +| params.ids['household id'] | Optional; _Requires pairing with either "device id" or "single device id"_ | Boolean | __1.__ Should ftrack return "household id". Set to `true` to attempt to return it. If set to `undefined` or `false`, ftrack will not return "household id". Default is `false`. __2.__ _This will only return "household id" if value of this field is `true` **AND** "household id" is defined on the device._ __3.__ _"household id" requires either "device id" or "single device id" to be also set to `true`, otherwise ftrack will not return "household id"._ | `true` | | storage | Required | Object | Storage settings for how the User ID module will cache the FTrack ID locally | | | storage.type | Required | String | This is where the results of the user ID will be stored. FTrack **requires** `"html5"`. | `"html5"` | | storage.name | Required | String | The name of the local storage where the user ID will be stored. FTrack **requires** `"FTrackId"`. | `"FTrackId"` | @@ -69,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 67fc9910825..26afe6e6b87 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, @@ -111,17 +103,11 @@ export const spec = { source: {ext: {}}, regs: {ext: {}} }; - const gdprConsent = bidderRequest.gdprConsent; - if (gdprConsent && gdprConsent.consentString && gdprConsent.gdprApplies) { - rtbBidRequest.ext.gdpr_consent = { - consent_string: gdprConsent.consentString, - consent_required: gdprConsent.gdprApplies - }; - - deepSetValue(rtbBidRequest, 'regs.ext.gdpr', gdprConsent.gdprApplies === true ? 1 : 0); - deepSetValue(rtbBidRequest, 'user.ext.consent', gdprConsent.consentString); - } + const gdprConsent = getGdprConsent(bidderRequest); + rtbBidRequest.ext.gdpr_consent = gdprConsent; + deepSetValue(rtbBidRequest, 'regs.ext.gdpr', gdprConsent.consent_required === true ? 1 : 0); + deepSetValue(rtbBidRequest, 'user.ext.consent', gdprConsent.consent_string); if (validBidRequests[0].schain) { deepSetValue(rtbBidRequest, 'source.ext.schain', validBidRequests[0].schain); @@ -133,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', @@ -149,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 } }); @@ -163,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 }, @@ -197,6 +183,7 @@ export const spec = { if (bidRequest && bidRequest.userId) { addExternalUserId(eids, deepAccess(bidRequest, `userId.id5id.uid`), 'id5-sync.com', 'ID5ID'); addExternalUserId(eids, deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 'TDID'); + addExternalUserId(eids, deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 'idl'); } if (eids.length > 0) { rtbBidRequest.user.ext.eids = eids; @@ -373,4 +360,20 @@ function replaceMacros(url, macros) { .replace('[US_PRIVACY]', macros.uspConsent); } +function getGdprConsent(bidderRequest) { + const gdprConsent = bidderRequest.gdprConsent; + + if (gdprConsent && gdprConsent.consentString && gdprConsent.gdprApplies) { + return { + consent_string: gdprConsent.consentString, + consent_required: gdprConsent.gdprApplies + }; + } + + return { + consent_required: false, + consent_string: '', + }; +} + registerBidder(spec); diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 161f530f202..9e300e1de97 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -43,7 +43,7 @@ const storageBlocked = []; const biddersBlocked = []; const analyticsBlocked = []; -let addedDeviceAccessHook = false; +let hooksAdded = false; // Helps in stubbing these functions in unit tests. export const internal = { @@ -174,29 +174,23 @@ export function deviceAccessHook(fn, gvlid, moduleName, result) { } 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); - if (isAllowed) { - result.valid = true; - fn.call(this, gvlid, moduleName, result); - } else { - curModule && logWarn(`TCF2 denied device access for ${curModule}`); - result.valid = false; - storageBlocked.push(curModule); - fn.call(this, gvlid, moduleName, result); - } + 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 = validateRules(purpose1Rule, consentData, curModule, gvlid); + if (isAllowed) { result.valid = true; fn.call(this, gvlid, moduleName, result); + } else { + curModule && logWarn(`TCF2 denied device access for ${curModule}`); + result.valid = false; + storageBlocked.push(curModule); + fn.call(this, gvlid, moduleName, result); } } else { result.valid = true; @@ -213,19 +207,14 @@ export function deviceAccessHook(fn, gvlid, moduleName, 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(); + 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); @@ -240,24 +229,19 @@ export function userSyncHook(fn, ...args) { */ 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); - } + 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); } @@ -272,25 +256,20 @@ 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; - }); + 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); } @@ -305,25 +284,20 @@ 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 (!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); } @@ -380,20 +354,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/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..af70c0c67f0 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; } diff --git a/modules/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js index 678e35729da..bbb4dbb30cd 100644 --- a/modules/glimpseBidAdapter.js +++ b/modules/glimpseBidAdapter.js @@ -1,18 +1,27 @@ -import { BANNER } from '../src/mediaTypes.js' -import { config } from '../src/config.js' -import { getStorageManager } from '../src/storageManager.js' -import { isArray } from '../src/utils.js' -import { registerBidder } from '../src/adapters/bidderFactory.js' - -const GVLID = 1012 -const BIDDER_CODE = 'glimpse' -const storageManager = getStorageManager({bidderCode: BIDDER_CODE}) -const ENDPOINT = 'https://api.glimpsevault.io/ads/serving/public/v1/prebid' +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { + isArray, + isEmpty, + isEmptyStr, + isStr, + isPlainObject, +} from '../src/utils.js'; + +const GVLID = 1012; +const BIDDER_CODE = 'glimpse'; +const storageManager = getStorageManager({ + gvlid: GVLID, + bidderCode: BIDDER_CODE, +}); +const ENDPOINT = 'https://market.glimpsevault.io/public/v1/prebid'; const LOCAL_STORAGE_KEY = { vault: { jwt: 'gp_vault_jwt', }, -} +}; export const spec = { gvlid: GVLID, @@ -20,126 +29,122 @@ export const spec = { supportedMediaTypes: [BANNER], /** - * Determines whether or not the given bid request is valid + * Determines if the bid request is valid * @param bid {BidRequest} The bid to validate * @return {boolean} */ isBidRequestValid: (bid) => { - return ( - hasValue(bid) && - hasValue(bid.params) && - hasStringValue(bid.params.placementId) - ) + const pid = bid?.params?.pid; + return isStr(pid) && !isEmptyStr(pid); }, /** - * Builds http request for Glimpse bids + * Builds the http request * @param validBidRequests {BidRequest[]} * @param bidderRequest {BidderRequest} * @returns {ServerRequest} */ buildRequests: (validBidRequests, bidderRequest) => { - const auth = getVaultJwt() - const referer = getReferer(bidderRequest) - const gdprConsent = getGdprConsentChoice(bidderRequest) - const bidRequests = validBidRequests.map(processBidRequest) - const firstPartyData = getFirstPartyData() + const url = buildQuery(bidderRequest); + const auth = getVaultJwt(); + const referer = getReferer(bidderRequest); + const imp = validBidRequests.map(processBidRequest); + const fpd = getFirstPartyData(bidderRequest.ortb2); const data = { auth, data: { referer, - gdprConsent, - bidRequests, - site: firstPartyData.site, - user: firstPartyData.user, - bidderCode: spec.code, - } - } + imp, + fpd, + }, + }; return { method: 'POST', - url: ENDPOINT, + url, data: JSON.stringify(data), options: {}, - } + }; }, /** - * Parse response from Glimpse server - * @param bidResponse {ServerResponse} + * Parse http response + * @param response {ServerResponse} * @returns {Bid[]} */ - interpretResponse: (bidResponse) => { - const isValidResponse = isValidBidResponse(bidResponse) - - if (isValidResponse) { - const {auth, data} = bidResponse.body - setVaultJwt(auth) - return data.bids + interpretResponse: (response) => { + if (isValidResponse(response)) { + const { auth, data } = response.body; + setVaultJwt(auth); + const bids = data.bids.map(processBidResponse); + return bids; } - - return [] + return []; }, -} +}; function setVaultJwt(auth) { - storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.vault.jwt, auth) + storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.vault.jwt, auth); } function getVaultJwt() { - return storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.vault.jwt) || '' + return ( + storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.vault.jwt) || '' + ); } function getReferer(bidderRequest) { - const hasReferer = - hasValue(bidderRequest) && - hasValue(bidderRequest.refererInfo) && - hasStringValue(bidderRequest.refererInfo.referer) - - if (hasReferer) { - return bidderRequest.refererInfo.referer - } - - return '' + // TODO: is 'page' the right value here? + return bidderRequest?.refererInfo?.page || ''; } -function getGdprConsentChoice(bidderRequest) { - const hasGdprConsent = - hasValue(bidderRequest) && - hasValue(bidderRequest.gdprConsent) +function buildQuery(bidderRequest) { + let url = appendQueryParam(ENDPOINT, 'ver', '$prebid.version$'); - if (hasGdprConsent) { - const gdprConsent = bidderRequest.gdprConsent - const hasGdprApplies = hasBooleanValue(gdprConsent.gdprApplies) + const timeout = config.getConfig('bidderTimeout'); + url = appendQueryParam(url, 'tmax', timeout); - return { - consentString: gdprConsent.consentString || '', - vendorData: gdprConsent.vendorData || {}, - gdprApplies: hasGdprApplies ? gdprConsent.gdprApplies : true, - } + if (gdprApplies(bidderRequest)) { + const consentString = bidderRequest.gdprConsent.consentString; + url = appendQueryParam(url, 'gdpr', consentString); } - return { - consentString: '', - vendorData: {}, - gdprApplies: false, + if (ccpaApplies(bidderRequest)) { + url = appendQueryParam(url, 'ccpa', bidderRequest.uspConsent); + } + + return url; +} + +function appendQueryParam(url, key, value) { + if (!value) { + return url; } + const prefix = url.includes('?') ? '&' : '?'; + return `${url}${prefix}${key}=${encodeURIComponent(value)}`; +} + +function gdprApplies(bidderRequest) { + return Boolean(bidderRequest?.gdprConsent?.gdprApplies); +} + +function ccpaApplies(bidderRequest) { + return ( + isStr(bidderRequest.uspConsent) && + !isEmptyStr(bidderRequest.uspConsent) && + bidderRequest.uspConsent?.substr(1, 3) !== '---' + ); } -function processBidRequest(bidRequest) { - const demand = bidRequest.params.demand || 'glimpse' - const sizes = normalizeSizes(bidRequest.sizes) - const keywords = bidRequest.params.keywords || {} +function processBidRequest(bid) { + const sizes = normalizeSizes(bid.sizes); return { - demand, + bid: bid.bidId, + pid: bid.params.pid, sizes, - keywords, - bidId: bidRequest.bidId, - placementId: bidRequest.params.placementId, - unitCode: bidRequest.adUnitCode, - } + }; } function normalizeSizes(sizes) { @@ -147,84 +152,51 @@ function normalizeSizes(sizes) { isArray(sizes) && sizes.length === 2 && !isArray(sizes[0]) && - !isArray(sizes[1]) + !isArray(sizes[1]); if (isSingleSize) { - return [sizes] + return [sizes]; } - return sizes -} - -function getFirstPartyData() { - const siteKeywords = parseGlobalKeywords('site') - const userKeywords = parseGlobalKeywords('user') - - const siteAttributes = getConfig('ortb2.site.ext.data', {}) - const userAttributes = getConfig('ortb2.user.ext.data', {}) - - return { - site: { - keywords: siteKeywords, - attributes: siteAttributes, - }, - user: { - keywords: userKeywords, - attributes: userAttributes, - }, - } -} - -function parseGlobalKeywords(scope) { - const keywords = getConfig(`ortb2.${scope}.keywords`, '') - - return keywords - .split(', ') - .filter((keyword) => keyword !== '') -} - -function getConfig(path, defaultValue) { - return config.getConfig(path) || defaultValue + return sizes; } -function isValidBidResponse(bidResponse) { - return ( - hasValue(bidResponse) && - hasValue(bidResponse.body) && - hasValue(bidResponse.body.data) && - hasArrayValue(bidResponse.body.data.bids) && - hasStringValue(bidResponse.body.auth) - ) +function getFirstPartyData(ortb2) { + let fpd = ortb2 || {}; + optimizeObject(fpd); + return fpd; } -function hasValue(value) { - return ( - value !== undefined && - value !== null - ) +function optimizeObject(obj) { + if (!isPlainObject(obj)) { + return; + } + for (const [key, value] of Object.entries(obj)) { + optimizeObject(value); + // only delete empty object, array, or string + if ( + (isPlainObject(value) || isArray(value) || isStr(value)) && + isEmpty(value) + ) { + delete obj[key]; + } + } } -function hasBooleanValue(value) { - return ( - hasValue(value) && - typeof value === 'boolean' - ) +function isValidResponse(bidResponse) { + const auth = bidResponse?.body?.auth; + const bids = bidResponse?.body?.data?.bids; + return isStr(auth) && isArray(bids) && !isEmpty(bids); } -function hasStringValue(value) { - return ( - hasValue(value) && - typeof value === 'string' && - value.length > 0 - ) -} +function processBidResponse(bid) { + const meta = bid.meta || {}; + meta.advertiserDomains = bid.meta?.advertiserDomains || []; -function hasArrayValue(value) { - return ( - hasValue(value) && - isArray(value) && - value.length > 0 - ) + return { + ...bid, + meta, + }; } -registerBidder(spec) +registerBidder(spec); diff --git a/modules/glimpseBidAdapter.md b/modules/glimpseBidAdapter.md index 767efcecf54..e82c5d8f32e 100644 --- a/modules/glimpseBidAdapter.md +++ b/modules/glimpseBidAdapter.md @@ -24,15 +24,14 @@ const adUnits = [ sizes: [[300, 250]], }, }, - bids: [{ - bidder: 'glimpse', - params: { - placementId: 'e53a7f564f8f44cc913b', - keywords: { - country: 'uk', + bids: [ + { + bidder: 'glimpse', + params: { + pid: 'e53a7f564f8f44cc913b', }, }, - }], + ], }, -] +]; ``` 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..9d5f6d71c6a 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -29,6 +29,7 @@ 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'; const BIDDER_CODE = 'goldbach'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -246,7 +247,8 @@ export const spec = { 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(',') @@ -267,7 +269,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); @@ -533,16 +534,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,7 +542,7 @@ function formatRequest(payload, bidderRequest) { let endpointUrl = URL; - if (!hasPurpose1Consent(bidderRequest)) { + if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { endpointUrl = URL_SIMPLE; } diff --git a/modules/googleAnalyticsAdapter.js b/modules/googleAnalyticsAdapter.js index b4c4c8c7009..88ce20d07fa 100644 --- a/modules/googleAnalyticsAdapter.js +++ b/modules/googleAnalyticsAdapter.js @@ -3,9 +3,9 @@ */ import { _each, logMessage } from '../src/utils.js'; -var events = require('../src/events.js'); -var CONSTANTS = require('../src/constants.json'); -var adapterManager = require('../src/adapterManager.js').default; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager.js'; var BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js index 1993f0c9b64..5f2d3c4f36a 100644 --- a/modules/gothamadsBidAdapter.js +++ b/modules/gothamadsBidAdapter.js @@ -74,8 +74,9 @@ export const spec = { 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/gravitoIdSystem.js b/modules/gravitoIdSystem.js new file mode 100644 index 00000000000..30fa3abd6d2 --- /dev/null +++ b/modules/gravitoIdSystem.js @@ -0,0 +1,57 @@ +/** + * This module adds gravitompId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/gravitoIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const storage = getStorageManager(); + +export const cookieKey = 'gravitompId'; + +export const gravitoIdSystemSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'gravitompId', + + /** + * performs action to obtain id + * @function + * @returns { {id: {gravitompId: string}} | undefined } + */ + getId: function() { + const newId = storage.getCookie(cookieKey); + if (!newId) { + return undefined; + } + const result = { + gravitompId: newId + } + return {id: result}; + }, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param { {gravitompId: string} } value + * @returns { {gravitompId: {id: string} } | undefined } + */ + decode: function(value) { + if (value && typeof value === 'object') { + const result = {}; + if (value.gravitompId) { + result.id = value.gravitompId + } + return {gravitompId: result}; + } + return undefined; + }, + +} + +submodule('userId', gravitoIdSystemSubmodule); diff --git a/modules/gravitoIdSystem.md b/modules/gravitoIdSystem.md new file mode 100644 index 00000000000..af4946779c5 --- /dev/null +++ b/modules/gravitoIdSystem.md @@ -0,0 +1,28 @@ +## Gravito User ID Submodule + +Gravito ID, provided by [Gravito Ltd.](https://gravito.net), is ID for ad targeting by using 1st party cookie. +Please contact Gravito Ltd. before using this ID. + +## Building Prebid with Gravito ID Support + +First, make sure to add the Gravito ID submodule to your Prebid.js package with: + +``` +gulp build --modules=gravitoIdSystem +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'gravitompId' + }] + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `"gravitompId"` | diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index e1090732071..40b79784595 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 || {}; @@ -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' @@ -284,13 +311,21 @@ export const spec = { if (genre && typeof genre === 'string') { request.site.content = {...request.site.content, genre}; } + const data = deepAccess(site, 'content.data'); + if (data && data.length) { + const siteContent = request.site.content || {}; + request.site.content = mergeDeep(siteContent, { data }); + } + const id = deepAccess(site, 'content.id'); + if (id) { + request.site.content = {...request.site.content, id}; + } } return { method: 'POST', - url: ENDPOINT_URL, + url: endpoint || ENDPOINT_URL, data: JSON.stringify(request), - newFormat: true, bidsMap }; }, @@ -299,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 = []; @@ -312,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 = ''; @@ -336,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 }; } } @@ -383,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); @@ -425,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) { @@ -559,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..b44f94f0495 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,9 +181,27 @@ 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 = { @@ -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..0fdca8265c6 100644 --- a/modules/growadvertisingBidAdapter.js +++ b/modules/growadvertisingBidAdapter.js @@ -101,7 +101,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..471b5e41ddc 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, @@ -370,6 +370,8 @@ 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() === 'skin') { + data.pi = 8; } } else { // legacy params data = { ...data, ...handleLegacyParams(params, sizes) } diff --git a/modules/gumgumBidAdapter.md b/modules/gumgumBidAdapter.md index 57d56235d1c..02b70b8367d 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 skin products should have a mediaType of banner. # Test Parameters ``` @@ -50,6 +50,24 @@ var adUnits = [ } } ] + },{ + code: 'skin-placement', + sizes: [[300, 50]], + mediaTypes: { + banner: { + sizes: [[1, 1]], + } + }, + bids: [ + { + bidder: 'gumgum', + params: { + zone: 'dc9d6be1', // GumGum Zone ID given to the client + product: 'skin', + 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..29d8bfa5e0f 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, diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js new file mode 100644 index 00000000000..c0e39925b4a --- /dev/null +++ b/modules/hadronAnalyticsAdapter.js @@ -0,0 +1,200 @@ +import { ajax } from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; +import CONSTANTS from '../src/constants.json'; +import { getStorageManager } from '../src/storageManager.js'; +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 HADRONID_ANALYTICS_VER = 'pbadgt0'; +const DEFAULT_PARTNER_ID = 0; +const AU_GVLID = 561; + +export const storage = getStorageManager(); + +var viewId = utils.generateUUID(); + +var partnerId = DEFAULT_PARTNER_ID; +var eventsToTrack = []; + +var w = window; +var d = document; +var e = d.documentElement; +var g = d.getElementsByTagName('body')[0]; +var x = w.innerWidth || e.clientWidth || g.clientWidth; +var y = w.innerHeight || e.clientHeight || g.clientHeight; + +var pageView = { + eventType: 'pageView', + userAgent: window.navigator.userAgent, + timestamp: Date.now(), + timezoneOffset: new Date().getTimezoneOffset(), + language: window.navigator.language, + vendor: window.navigator.vendor, + pageUrl: getRefererInfo().page, + screenWidth: x, + screenHeight: y +}; + +var eventQueue = [ + pageView +]; + +var startAuction = 0; +var bidRequestTimeout = 0; +let analyticsType = 'endpoint'; + +let hadronAnalyticsAdapter = Object.assign(adapter({url: HADRON_ANALYTICS_URL, analyticsType}), { + track({eventType, args}) { + args = args ? JSON.parse(JSON.stringify(args)) : {}; + var data = {}; + if (!eventsToTrack.includes(eventType)) return; + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: { + data = args; + startAuction = data.timestamp; + bidRequestTimeout = data.timeout; + break; + } + + case CONSTANTS.EVENTS.AUCTION_END: { + data = args; + data.start = startAuction; + data.end = Date.now(); + break; + } + + case CONSTANTS.EVENTS.BID_ADJUSTMENT: { + data.bidders = args; + break; + } + + case CONSTANTS.EVENTS.BID_TIMEOUT: { + data.bidders = args; + data.duration = bidRequestTimeout; + break; + } + + case CONSTANTS.EVENTS.BID_REQUESTED: { + data = args; + break; + } + + case CONSTANTS.EVENTS.BID_RESPONSE: { + data = args; + delete data.ad; + break; + } + + case CONSTANTS.EVENTS.BID_WON: { + data = args; + delete data.ad; + delete data.adUrl; + break; + } + + case CONSTANTS.EVENTS.BIDDER_DONE: { + data = args; + break; + } + + case CONSTANTS.EVENTS.SET_TARGETING: { + data.targetings = args; + break; + } + + case CONSTANTS.EVENTS.REQUEST_BIDS: { + data = args; + break; + } + + case CONSTANTS.EVENTS.ADD_AD_UNITS: { + data = args; + break; + } + + case CONSTANTS.EVENTS.AD_RENDER_FAILED: { + data = args; + break; + } + + default: + return; + } + + data.eventType = eventType; + data.timestamp = data.timestamp || Date.now(); + + sendEvent(data); + } +}); + +hadronAnalyticsAdapter.originEnableAnalytics = hadronAnalyticsAdapter.enableAnalytics; + +hadronAnalyticsAdapter.enableAnalytics = function(conf = {}) { + if (typeof conf.options === 'object') { + if (conf.options.partnerId) { + partnerId = conf.options.partnerId; + } else { + partnerId = DEFAULT_PARTNER_ID; + } + if (conf.options.eventsToTrack) { + eventsToTrack = conf.options.eventsToTrack; + } + } else { + utils.logError('HADRON_ANALYTICS_NO_CONFIG_ERROR'); + return; + } + + hadronAnalyticsAdapter.originEnableAnalytics(conf); +} + +function flush() { + // Don't send anything if no partner id was declared + if (partnerId === DEFAULT_PARTNER_ID) return; + if (eventQueue.length > 1) { + var data = { + pageViewId: viewId, + ver: HADRONID_ANALYTICS_VER, + partnerId: partnerId, + events: eventQueue + }; + + ajax(HADRON_ANALYTICS_URL, + () => utils.logInfo('HADRON_ANALYTICS_BATCH_SEND'), + JSON.stringify(data), + { + contentType: 'application/json', + method: 'POST' + } + ); + + eventQueue = [ + pageView + ]; + } +} + +function sendEvent(event) { + eventQueue.push(event); + utils.logInfo(`HADRON_ANALYTICS_EVENT ${event.eventType} `, event); + + if (event.eventType === CONSTANTS.EVENTS.AUCTION_END) { + flush(); + } +} + +adapterManager.registerAnalyticsAdapter({ + adapter: hadronAnalyticsAdapter, + code: 'hadronAnalytics', + gvlid: AU_GVLID +}); + +hadronAnalyticsAdapter.flush = flush; + +export default hadronAnalyticsAdapter; diff --git a/modules/hadronAnalyticsAdapter.md b/modules/hadronAnalyticsAdapter.md new file mode 100644 index 00000000000..8a41be8d36e --- /dev/null +++ b/modules/hadronAnalyticsAdapter.md @@ -0,0 +1,48 @@ +# Overview +Module Name: Hadron Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: [audigent.com](https://audigent.com) + +# Hadron ID + +The Hadron ID is a container that publishers and ad tech platforms can use to +recognise users' segments where 3rd party cookies are not available. +The Hadron ID is designed to respect users' privacy choices and publishers’ +preferences throughout the advertising value chain. +For more information about the Hadron ID and detailed integration docs, please visit +[our brochure](https://audigent.com/hadron-id). + +# Hadron Analytics Registration + +The Hadron Analytics Adapter is free to use for our customers. +Please visit [audigent/hadron-id](https://audigent.com/hadron-id) to request a demo or get more info. + +The partners' privacy policy is at [https://audigent.com/privacypolicy/#partners](https://audigent.com/privacypolicy/#partners). + +## Hadron Analytics Configuration + +First, make sure to add the Hadron Analytics submodule to your Prebid.js package with: + +``` +gulp build --modules=...,hadronAnalyticsAdapter +``` + +The following configuration parameters are available: + +```javascript +pbjs.enableAnalytics({ + provider: 'hadronAnalytics', + options: { + partnerId: 1234, // change to the Partner ID you got from Audigent + eventsToTrack: ['auctionEnd','bidWon'] + } +}); +``` + +| 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 0b1081f174a..a6aa0197d55 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -10,16 +10,28 @@ 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'; +import {isFn, isStr, isArray, deepEqual, isPlainObject, logError, logInfo} from '../src/utils.js'; +import {loadExternalScript} from '../src/adloader.js'; +const LOG_PREFIX = 'User ID - HadronRtdProvider submodule: '; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'hadron'; const AU_GVLID = 561; - +const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebid'; +const HADRON_SEGMENT_URL = 'https://seg.hadron.ad.gt/api/v1/rtd'; 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 @@ -92,8 +104,9 @@ function mergeLazy(target, source) { /** * Param or default. - * @param {String} param + * @param {String|Function} param * @param {String} defaultVal + * @param {Object} arg */ function paramOrDefault(param, defaultVal, arg) { if (isFn(param)) { @@ -114,34 +127,20 @@ 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]))); } } } /** * Real-time data retrieval from Audigent - * @param {Object} reqBidsConfigObj + * @param {Object} bidConfig * @param {function} onDone * @param {Object} rtdConfig * @param {Object} userConsent @@ -161,30 +160,35 @@ 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 { - var script = document.createElement('script'); - script.type = 'text/javascript'; - window.pubHadronCb = (hadronId) => { userIds.hadronId = hadronId; getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); } - + const partnerId = rtdConfig.params.partnerId | 0; const hadronIdUrl = rtdConfig.params && rtdConfig.params.hadronIdUrl; - script.src = paramOrDefault(hadronIdUrl, 'https://id.hadron.ad.gt/api/v1/hadronid', userIds); - document.getElementsByTagName('head')[0].appendChild(script); + const scriptUrl = urlAddParams( + paramOrDefault(hadronIdUrl, HADRON_ID_DEFAULT_URL, userIds), + `partner_id=${partnerId}&_it=prebid` + ); + loadExternalScript(scriptUrl, 'hadron', () => { + logInfo(LOG_PREFIX, 'hadronIdTag loaded', scriptUrl); + }) } } /** * Async rtd retrieval from Audigent + * @param {Object} bidConfig * @param {function} onDone * @param {Object} rtdConfig * @param {Object} userConsent @@ -194,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; } @@ -202,8 +206,7 @@ export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, reqParams.pubHadronPm = window.pubHadronPm; } - const url = `https://seg.hadron.ad.gt/api/v1/rtd`; - ajax(url, { + ajax(HADRON_SEGMENT_URL, { success: function (response, req) { if (req.status === 200) { try { @@ -237,7 +240,7 @@ export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, /** * Module init * @param {Object} provider - * @param {Objkect} userConsent + * @param {Object} userConsent * @return {boolean} */ function init(provider, userConsent) { 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/iasRtdProvider.js b/modules/iasRtdProvider.js index 6f7b2d5215d..58899d7a8c0 100644 --- a/modules/iasRtdProvider.js +++ b/modules/iasRtdProvider.js @@ -13,6 +13,28 @@ const FRAUD_FIELD_NAME = 'fr'; const SLOTS_OBJECT_FIELD_NAME = 'slots'; const CUSTOM_FIELD_NAME = 'custom'; const IAS_KW = 'ias-kw'; +const IAS_KEY_MAPPINGS = { + adt: 'adt', + alc: 'alc', + dlm: 'dlm', + hat: 'hat', + off: 'off', + vio: 'vio', + drg: 'drg', + 'ias-kw': 'ias-kw', + fr: 'fr', + vw: 'vw', + grm: 'grm', + pub: 'pub', + vw05: 'vw05', + vw10: 'vw10', + vw15: 'vw15', + vw30: 'vw30', + vw_vv: 'vw_vv', + grm_vv: 'grm_vv', + pub_vv: 'pub_vv', + id: 'id' +}; /** * Module init @@ -26,6 +48,14 @@ export function init(config, userConsent) { utils.logError('missing pubId param for IAS provider'); return false; } + if (params.hasOwnProperty('keyMappings')) { + const keyMappings = params.keyMappings; + for (let prop in keyMappings) { + if (IAS_KEY_MAPPINGS.hasOwnProperty(prop)) { + IAS_KEY_MAPPINGS[prop] = keyMappings[prop] + } + } + } return true; } @@ -62,6 +92,16 @@ function stringifyScreenSize() { return [(window.screen && window.screen.width) || -1, (window.screen && window.screen.height) || -1].join('.'); } +function renameKeyValues(source) { + let result = {}; + for (let prop in IAS_KEY_MAPPINGS) { + if (source.hasOwnProperty(prop)) { + result[IAS_KEY_MAPPINGS[prop]] = source[prop]; + } + } + return result; +} + function formatTargetingData(adUnit) { let result = {}; if (iasTargeting[BRAND_SAFETY_OBJECT_FIELD_NAME]) { @@ -76,7 +116,7 @@ function formatTargetingData(adUnit) { if (iasTargeting[SLOTS_OBJECT_FIELD_NAME] && adUnit in iasTargeting[SLOTS_OBJECT_FIELD_NAME]) { utils.mergeDeep(result, iasTargeting[SLOTS_OBJECT_FIELD_NAME][adUnit]); } - return result; + return renameKeyValues(result); } function constructQueryString(anId, adUnits) { @@ -147,6 +187,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { undefined, { method: 'GET' } ); + callback() } /** @type {RtdSubmodule} */ diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index f2143c1cced..96ec1fed754 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -5,7 +5,16 @@ * @requires module:modules/userId */ -import { deepAccess, logInfo, deepSetValue, logError, isEmpty, isEmptyStr, logWarn } from '../src/utils.js'; +import { + deepAccess, + logInfo, + deepSetValue, + logError, + isEmpty, + isEmptyStr, + logWarn, + safeJSONParse +} from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -24,7 +33,7 @@ const LOG_PREFIX = 'User ID - ID5 submodule: '; // cookie in the array is the most preferred to use const LEGACY_COOKIE_NAMES = [ 'pbjs-id5id', 'id5id.1st', 'id5id' ]; -const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const id5IdSubmodule = { @@ -113,7 +122,7 @@ export const id5IdSubmodule = { 'gdpr': hasGdpr, 'nbPage': incrementNb(config.params.partner), 'o': 'pbjs', - 'rf': referer.referer, + 'rf': referer.topmostLocation, 'top': referer.reachedTop ? 1 : 0, 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$' @@ -253,7 +262,7 @@ function getLegacyCookieSignature() { let legacyStoredValue; LEGACY_COOKIE_NAMES.forEach(function(cookie) { if (storage.getCookie(cookie)) { - legacyStoredValue = JSON.parse(storage.getCookie(cookie)) || legacyStoredValue; + legacyStoredValue = safeJSONParse(storage.getCookie(cookie)) || legacyStoredValue; } }); return (legacyStoredValue && legacyStoredValue.signature) || ''; 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..0ed239019cf 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -42,14 +42,6 @@ function setImDataInCookie(value) { */ 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) { if (data.im_segments && data.im_segments.length) { const dctr = deepAccess(bid, 'params.dctr'); @@ -96,9 +88,8 @@ export function setRealTimeData(bidConfig, moduleConfig, data) { const utils = {deepSetValue, deepAccess, logInfo, logError, mergeDeep}; if (data.im_segments) { - const ortb2 = config.getConfig('ortb2') || {}; + const ortb2 = bidConfig.ortb2Fragments?.global || {}; deepSetValue(ortb2, 'user.ext.data.im_segments', data.im_segments); - config.setConfig({ortb2: ortb2}); if (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues')) { window.googletag = window.googletag || {cmd: []}; 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..bb3ad63085c 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -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 18016eea530..a7a97f4c839 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,73 +1,77 @@ import { - _each, + cleanObj, deepAccess, + deepClone, deepSetValue, getBidIdParameter, getBidRequest, + getDNT, getUniqueIdentifierStr, - isArray, - isEmpty, isFn, - isInteger, - isNumber, isPlainObject, - isStr, - logError, - logWarn, mergeDeep + 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 {includes} from '../src/polyfill.js'; +import {hasPurpose1Consent} from '../src/utils/gpdr.js'; const BIDDER_CODE = 'improvedigital'; -const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; +const CREATIVE_TTL = 300; + +const AD_SERVER_URL = 'https://ad.360yield.com/pb'; +const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; +const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html'; + +const VIDEO_PARAMS = { + DEFAULT_MIMES: ['video/mp4'], + SUPPORTED_PROPERTIES: ['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', 'ext'], + PLACEMENT_TYPE: { + INSTREAM: 1, + OUTSTREAM: 3, + } +}; -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); - } +const NATIVE_DATA = { + VERSION: '1.2', + ASSET_TYPES: { + TITLE: 'title', + IMG: 'img', + DATA: 'data', }, - - isValidBid(bid) { - return bid && /razr:\\?\/\\?\//.test(bid.ad); + ASSETS: { + title: {id: 0, name: 'title', assetType: 'title', default: {len: 140}}, + sponsoredBy: {id: 1, name: 'sponsoredBy', assetType: 'data', type: 1}, + icon: {id: 2, name: 'icon', assetType: 'img', type: 2}, + body: {id: 3, name: 'body', assetType: 'data', type: 2}, + image: {id: 4, name: 'image', assetType: 'img', type: 3}, + rating: {id: 5, name: 'rating', assetType: 'data', type: 3}, + likes: {id: 6, name: 'likes', assetType: 'data', type: 4}, + downloads: {id: 7, name: 'downloads', assetType: 'data', type: 5}, + price: {id: 8, name: 'price', assetType: 'data', type: 6}, + salePrice: {id: 9, name: 'salePrice', assetType: 'data', type: 7}, + phone: {id: 10, name: 'phone', assetType: 'data', type: 8}, + address: {id: 11, name: 'address', assetType: 'data', type: 9}, + body2: {id: 12, name: 'body2', assetType: 'data', type: 10}, + displayUrl: {id: 13, name: 'displayUrl', assetType: 'data', type: 11}, + cta: {id: 14, name: 'cta', assetType: 'data', type: 12}, }, - - render(bid) { - const {bidRequest} = bid.renderer.getConfig(); - - const payload = { - type: 'prebid', - bidRequest, - bid, - config: mergeDeep( - {}, - config.getConfig('improvedigital.rendererConfig'), - deepAccess(bidRequest, 'params.rendererConfig') - ) - }; - - const razr = window.razr = window.razr || {}; - razr.queue = razr.queue || []; - razr.queue.push(payload); + getAssetById(id) { + return Object.values(this.ASSETS).find(asset => id === asset.id); } }; export const spec = { - version: '7.7.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], supportedMediaTypes: [BANNER, NATIVE, VIDEO], + syncStore: { extendMode: false, placementId: null }, /** * Determines whether or not the given bid request is valid. @@ -75,7 +79,7 @@ export const spec = { * @param {object} bid The bid to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: function (bid) { + isBidRequestValid(bid) { return !!(bid && bid.params && (bid.params.placementId || (bid.params.placementKey && bid.params.publisherId))); }, @@ -83,179 +87,136 @@ export const spec = { * 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. + * @param bidderRequest * @return ServerRequest Info describing the request to the server. */ - buildRequests: function (bidRequests, bidderRequest) { - let normalizedBids = bidRequests.map((bidRequest) => { - return getNormalizedBidRequest(bidRequest); - }); - - let idClient = new ImproveDigitalAdServerJSClient('hb'); - let requestParameters = { - singleRequestMode: (config.getConfig('improvedigital.singleRequest') === true), - returnObjType: idClient.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT, - libVersion: this.version - }; - - const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') - if (gdprConsent) { - // GDPR Consent String - if (gdprConsent.consentString) { - requestParameters.gdpr = gdprConsent.consentString; + buildRequests(bidRequests, bidderRequest) { + const request = { + cur: [config.getConfig('currency.adServerCurrency') || 'USD'], + ext: { + improvedigital: { + sdk: { + name: 'pbjs', + version: '$prebid.version$', + } + } } + }; - // Additional Consent String - const additionalConsent = deepAccess(gdprConsent, 'addtlConsent'); - if (additionalConsent && additionalConsent.indexOf('~') !== -1) { - // Google Ad Tech Provider IDs - const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1); - deepSetValue( - requestParameters, - 'user.ext.consented_providers_settings.consented_providers', - atpIds.split('.').map(id => parseInt(id, 10)) - ); - } + // Device + request.device = (typeof config.getConfig('device') === 'object') ? config.getConfig('device') : {}; + request.device.w = request.device.w || window.innerWidth; + request.device.h = request.device.h || window.innerHeight; + if (getDNT()) { + request.device.dnt = 1; } - if (bidderRequest && bidderRequest.uspConsent) { - requestParameters.usPrivacy = bidderRequest.uspConsent; + // Coppa + const coppa = config.getConfig('coppa'); + if (typeof coppa === 'boolean') { + deepSetValue(request, 'regs.coppa', Number(coppa)); } - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - requestParameters.referrer = bidderRequest.refererInfo.referer; - } + if (bidderRequest) { + // GDPR + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + deepSetValue(request, 'regs.ext.gdpr', Number(gdprConsent.gdprApplies)); + } + deepSetValue(request, 'user.ext.consent', gdprConsent.consentString); + + // Additional Consent String + const additionalConsent = deepAccess(gdprConsent, 'addtlConsent'); + if (additionalConsent && additionalConsent.indexOf('~') !== -1) { + // Google Ad Tech Provider IDs + const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1); + if (atpIds) { + deepSetValue( + request, + 'user.ext.consented_providers_settings.consented_providers', + atpIds.split('.').map(id => parseInt(id, 10)) + ); + } + } + } - // Adding first party data - const site = config.getConfig('ortb2.site'); - if (site) { - const pageCategory = site.pagecat || site.cat; - if (pageCategory && isArray(pageCategory)) { - requestParameters.pagecat = pageCategory.filter((category) => { - return category && isStr(category) - }); + // Timeout + if (bidderRequest.timeout) { + request.tmax = parseInt(bidderRequest.timeout); } - const genre = deepAccess(site, 'content.genre'); - if (genre && isStr(genre)) { - requestParameters.genre = genre; + // US Privacy + if (typeof bidderRequest.uspConsent !== typeof undefined) { + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); } } - // End of adding first party data - requestParameters.schain = bidRequests[0].schain; - requestParameters.coppa = config.getConfig('coppa') === true; + ID_REQUEST.buildSiteOrApp(request, bidderRequest); - if (bidRequests[0].userId) { - const eids = createEidsArray(bidRequests[0].userId); - if (eids.length) { - deepSetValue(requestParameters, 'user.ext.eids', eids); - } - } + const bidRequest0 = bidRequests[0]; - let requestObj = idClient.createRequest( - normalizedBids, // requestObject - requestParameters - ); + deepSetValue(request, 'source.ext.schain', bidRequest0.schain); + deepSetValue(request, 'source.tid', bidRequest0.transactionId); - if (requestObj.errors && requestObj.errors.length > 0) { - logError('ID WARNING 0x01'); + // Save a placement id to send it to the ad server when fetching the user syncs + this.syncStore.placementId = this.syncStore.placementId || bidRequest0.params.placementId; + + if (bidRequest0.userId) { + const eids = createEidsArray(bidRequest0.userId); + deepSetValue(request, 'user.ext.eids', eids.length ? eids : undefined); } - requestObj.requests.forEach(request => request.bidderRequest = bidderRequest); - return requestObj.requests; + + return ID_REQUEST.buildServerRequests(request, bidRequests, bidderRequest); }, /** * Unpack the response from the server into a list of bids. * * @param {*} serverResponse A successful response from the server. + * @param bidderRequest * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse, {bidderRequest}) { + interpretResponse(serverResponse, { bidderRequest }) { + if (!Array.isArray(deepAccess(serverResponse, 'body.seatbid'))) { + return []; + } + const bids = []; - _each(serverResponse.body.bid, function (bidObject) { - if (!bidObject.price || bidObject.price === null || - bidObject.hasOwnProperty('errorCode') || - (!bidObject.adm && !bidObject.native)) { - return; - } - const bidRequest = getBidRequest(bidObject.id, [bidderRequest]); - const bid = {}; - - if (bidObject.native) { - // Native - bid.native = getNormalizedNativeAd(bidObject.native); - // Expose raw oRTB response to the client to allow parsing assets not directly supported by Prebid - bid.ortbNative = bidObject.native; - if (bidObject.nurl) { - bid.native.impressionTrackers.unshift(bidObject.nurl); - } - bid.mediaType = NATIVE; - } else if (bidObject.ad_type && bidObject.ad_type === 'video') { - bid.vastXml = bidObject.adm; - bid.mediaType = VIDEO; - if (isOutstreamVideo(bidRequest)) { - bid.adResponse = { - content: bid.vastXml, - height: bidObject.h, - width: bidObject.w - }; - bid.renderer = createRenderer(bidRequest); - } - } else { - // Banner - let nurl = ''; - if (bidObject.nurl && bidObject.nurl.length > 0) { - nurl = ``; - } - bid.ad = `${nurl}`; - bid.mediaType = BANNER; - } - // Common properties - bid.cpm = parseFloat(bidObject.price); - bid.creativeId = bidObject.crid; - bid.currency = bidObject.currency ? bidObject.currency.toUpperCase() : 'USD'; - - // Deal ID. Composite ads can have multiple line items and the ID of the first - // dealID line item will be used. - if (isNumber(bidObject.lid) && bidObject.buying_type && bidObject.buying_type !== 'rtb') { - bid.dealId = bidObject.lid; - } else if (Array.isArray(bidObject.lid) && - Array.isArray(bidObject.buying_type) && - bidObject.lid.length === bidObject.buying_type.length) { - let isDeal = false; - bidObject.buying_type.forEach((bt, i) => { - if (isDeal) return; - if (bt && bt !== 'rtb') { - isDeal = true; - bid.dealId = bidObject.lid[i]; - } - }); - } + serverResponse.body.seatbid.forEach(seatbid => { + if (!Array.isArray(seatbid.bid)) return; - bid.height = bidObject.h; - bid.netRevenue = bidObject.isNet ? bidObject.isNet : false; - bid.requestId = bidObject.id; - bid.ttl = 300; - bid.width = bidObject.w; + seatbid.bid.forEach(bidObject => { + if (!bidObject.adm || !bidObject.price || bidObject.hasOwnProperty('errorCode')) { + return; + } + const bidRequest = getBidRequest(bidObject.impid, [bidderRequest]); + const idExt = deepAccess(bidObject, `ext.${BIDDER_CODE}`, {}); + + const bid = { + requestId: bidObject.impid, + cpm: bidObject.price, + creativeId: bidObject.crid, + currency: serverResponse.body.cur.toUpperCase() || 'USD', + dealId: (typeof idExt.buying_type === 'string' && idExt.buying_type !== 'rtb') ? idExt.line_item_id : undefined, + meta: { + advertiserDomains: bidObject.adomain ? bidObject.adomain : [] + }, + netRevenue: idExt.is_net || false, + ttl: CREATIVE_TTL + } - if (!bid.width || !bid.height) { - bid.width = 1; - bid.height = 1; - } + ID_RESPONSE.buildAd(bid, bidRequest, bidObject); - if (bidObject.adomain) { - bid.meta = { - advertiserDomains: bidObject.adomain - }; - } + ID_RAZR.addBidData({ + bidRequest, + bid + }); - ID_RAZR.addBidData({ - bidRequest, - bid + bids.push(bid); }); - - bids.push(bid); }); + return bids; }, @@ -266,549 +227,443 @@ export const spec = { * @param {ServerResponse[]} serverResponses List of server's responses. * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function(syncOptions, serverResponses) { - if (syncOptions.pixelEnabled) { - const syncs = []; + getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (config.getConfig('coppa') === true || !hasPurpose1Consent(gdprConsent)) { + return []; + } + + const syncs = []; + if ((this.syncStore.extendMode || !syncOptions.pixelEnabled) && syncOptions.iframeEnabled) { + const { gdprApplies, consentString } = gdprConsent || {}; + syncs.push({ + type: 'iframe', + url: IFRAME_SYNC_URL + + `?placement_id=${this.syncStore.placementId}` + + (this.syncStore.extendMode ? '&pbs=1' : '') + + (typeof gdprApplies === 'boolean' ? `&gdpr=${Number(gdprApplies)}` : '') + + (consentString ? `&gdpr_consent=${consentString}` : '') + + (uspConsent ? `&us_privacy=${encodeURIComponent(uspConsent)}` : '') + }); + } else if (syncOptions.pixelEnabled) { serverResponses.forEach(response => { - response.body.bid.forEach(bidObject => { - if (isArray(bidObject.sync)) { - bidObject.sync.forEach(syncElement => { - if (syncs.indexOf(syncElement) === -1) { - syncs.push(syncElement); - } - }); + const syncArr = deepAccess(response, `body.ext.${BIDDER_CODE}.sync`, []); + syncArr.forEach(url => { + if (!syncs.some(sync => sync.url === url)) { + syncs.push({ type: 'image', url }); } }); }); - return syncs.map(sync => ({ type: 'image', url: sync })); } - return []; + + return syncs; } }; -function isInstreamVideo(bid) { - const mediaTypes = Object.keys(deepAccess(bid, 'mediaTypes', {})); - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); - const context = deepAccess(bid, 'mediaTypes.video.context'); - return bid.mediaType === 'video' || (mediaTypes.length === 1 && videoMediaType && context !== 'outstream'); -} - -function isOutstreamVideo(bid) { - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); - const context = deepAccess(bid, 'mediaTypes.video.context'); - return videoMediaType && context === 'outstream'; -} - -function getVideoTargetingParams(bid) { - const result = {}; - Object.keys(Object(bid.mediaTypes.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.mediaTypes.video[ key ]; - }); - Object.keys(Object(bid.params.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.params.video[ key ]; +registerBidder(spec); + +const ID_REQUEST = { + buildServerRequests(basicRequest, bidRequests, bidderRequest) { + const globalExtendMode = config.getConfig('improvedigital.extend') === true; + const requests = []; + const singleRequestMode = config.getConfig('improvedigital.singleRequest') === true; + + const extendImps = []; + const adServerImps = []; + + function formatRequest(imps, transactionId, extendMode) { + const request = deepClone(basicRequest); + request.imp = imps; + request.id = getUniqueIdentifierStr(); + if (transactionId) { + deepSetValue(request, 'source.tid', transactionId); + } + return { + method: 'POST', + url: extendMode ? EXTEND_URL : AD_SERVER_URL, + data: JSON.stringify(request), + bidderRequest + } + }; + + bidRequests.map((bidRequest) => { + const extendModeEnabled = this.isExtendModeEnabled(globalExtendMode, bidRequest.params); + const imp = this.buildImp(bidRequest, extendModeEnabled); + if (singleRequestMode) { + extendModeEnabled ? extendImps.push(imp) : adServerImps.push(imp); + } else { + requests.push(formatRequest([imp], bidRequest.transactionId, extendModeEnabled)); + } }); - return result; -} -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return null; - } - const floor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; -} - -function outstreamRender(bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - sizes: [bid.width, bid.height], - targetId: bid.adUnitCode, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }, handleOutstreamRendererEvents.bind(null, bid)); - }); -} - -function handleOutstreamRendererEvents(bid, id, eventName) { - bid.renderer.handleVideoEvent({ id, eventName }); -} - -function createRenderer(bidRequest) { - const renderer = Renderer.install({ - id: bidRequest.adUnitCode, - url: RENDERER_URL, - loaded: false, - config: deepAccess(bidRequest, 'renderer.options'), - adUnitCode: bidRequest.adUnitCode - }); - try { - renderer.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); - } - return renderer; -} - -function getNormalizedBidRequest(bid) { - let adUnitId = getBidIdParameter('adUnitCode', bid) || null; - let placementId = getBidIdParameter('placementId', bid.params) || null; - let publisherId = null; - let placementKey = null; - - if (placementId === null) { - publisherId = getBidIdParameter('publisherId', bid.params) || null; - placementKey = getBidIdParameter('placementKey', bid.params) || null; - } - const keyValues = getBidIdParameter('keyValues', bid.params) || null; - const singleSizeFilter = getBidIdParameter('size', bid.params) || null; - const bidId = getBidIdParameter('bidId', bid); - const transactionId = getBidIdParameter('transactionId', bid); - const currency = config.getConfig('currency.adServerCurrency'); - - let normalizedBidRequest = {}; - if (isInstreamVideo(bid)) { - normalizedBidRequest.adTypes = [ VIDEO ]; - } - if (isInstreamVideo(bid) || isOutstreamVideo(bid)) { - normalizedBidRequest.video = getVideoTargetingParams(bid); - } - if (placementId) { - normalizedBidRequest.placementId = placementId; - } else { - if (publisherId) { - normalizedBidRequest.publisherId = publisherId; + if (!singleRequestMode) { + return requests; } - if (placementKey) { - normalizedBidRequest.placementKey = placementKey; + // In the single request mode, split imps between those going to the ad server and those going to extend server + if (extendImps.length) { + requests.push(formatRequest(extendImps, null, true)); + } + if (adServerImps.length) { + requests.push(formatRequest(adServerImps, null, false)); } - } - - if (keyValues) { - normalizedBidRequest.keyValues = keyValues; - } - - if (config.getConfig('improvedigital.usePrebidSizes') === true && !isInstreamVideo(bid) && !isOutstreamVideo(bid) && bid.sizes && bid.sizes.length > 0) { - normalizedBidRequest.format = bid.sizes; - } else if (singleSizeFilter && singleSizeFilter.w && singleSizeFilter.h) { - normalizedBidRequest.size = {}; - normalizedBidRequest.size.h = singleSizeFilter.h; - normalizedBidRequest.size.w = singleSizeFilter.w; - } - if (bidId) { - normalizedBidRequest.id = bidId; - } - if (adUnitId) { - normalizedBidRequest.adUnitId = adUnitId; - } - if (transactionId) { - normalizedBidRequest.transactionId = transactionId; - } - if (currency) { - normalizedBidRequest.currency = currency; - } - // Floor - let bidFloor = getBidFloor(bid); - let bidFloorCur = null; - if (!bidFloor) { - bidFloor = getBidIdParameter('bidFloor', bid.params); - bidFloorCur = getBidIdParameter('bidFloorCur', bid.params); - } - if (bidFloor) { - normalizedBidRequest.bidFloor = bidFloor; - normalizedBidRequest.bidFloorCur = bidFloorCur ? bidFloorCur.toUpperCase() : 'USD'; - } - return normalizedBidRequest; -} + return requests; + }, -function getNormalizedNativeAd(rawNative) { - const native = {}; - if (!rawNative || !isArray(rawNative.assets)) { - return null; - } - // Assets - rawNative.assets.forEach(asset => { - if (asset.title) { - native.title = asset.title.text; - } else if (asset.data) { - switch (asset.data.type) { - case 1: - native.sponsoredBy = asset.data.value; - break; - case 2: - native.body = asset.data.value; - break; - case 3: - native.rating = asset.data.value; - break; - case 4: - native.likes = asset.data.value; - break; - case 5: - native.downloads = asset.data.value; - break; - case 6: - native.price = asset.data.value; - break; - case 7: - native.salePrice = asset.data.value; - break; - case 8: - native.phone = asset.data.value; - break; - case 9: - native.address = asset.data.value; - break; - case 10: - native.body2 = asset.data.value; - break; - case 11: - native.displayUrl = asset.data.value; - break; - case 12: - native.cta = asset.data.value; - break; - } - } else if (asset.img) { - switch (asset.img.type) { - case 2: - native.icon = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h - }; - break; - case 3: - native.image = { - url: asset.img.url, - width: asset.img.w, - height: asset.img.h - }; - break; - } + isExtendModeEnabled(globalExtendMode, bidParams) { + const extendMode = typeof bidParams.extend === 'boolean' ? bidParams.extend : globalExtendMode; + if (extendMode && !spec.syncStore.extendMode) { + spec.syncStore.extendMode = true; } - }); - // Trackers - if (rawNative.eventtrackers) { - native.impressionTrackers = []; - rawNative.eventtrackers.forEach(tracker => { - // Only handle impression event. Viewability events are not supported yet. - if (tracker.event !== 1) return; - switch (tracker.method) { - case 1: // img - native.impressionTrackers.push(tracker.url); - break; - case 2: // js - // javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. - native.javascriptTrackers = ``; - break; - } - }); - } else { - native.impressionTrackers = rawNative.imptrackers || []; - native.javascriptTrackers = rawNative.jstracker; - } - if (rawNative.link) { - native.clickUrl = rawNative.link.url; - native.clickTrackers = rawNative.link.clicktrackers; - } - if (rawNative.privacy) { - native.privacyLink = rawNative.privacy; - } - return native; -} -registerBidder(spec); + return extendMode; + }, -export function ImproveDigitalAdServerJSClient(endPoint) { - this.CONSTANTS = { - AD_SERVER_BASE_URL: 'ice.360yield.com', - END_POINT: endPoint || 'hb', - AD_SERVER_URL_PARAM: 'jsonp=', - CLIENT_VERSION: 'JS-6.4.0', - MAX_URL_LENGTH: 2083, - ERROR_CODES: { - MISSING_PLACEMENT_PARAMS: 2, - LIB_VERSION_MISSING: 3 - }, - RETURN_OBJ_TYPE: { - DEFAULT: 0, - URL_PARAMS_SPLIT: 1 - } - }; - - this.getErrorReturn = function(errorCode) { - return { - idMappings: {}, - requests: {}, - 'errorCode': errorCode + buildImp(bidRequest, extendMode) { + const imp = { + id: getBidIdParameter('bidId', bidRequest) || getUniqueIdentifierStr(), + secure: Number(window.location.protocol === 'https:'), }; - }; - this.createRequest = function(requestObject, requestParameters, extraRequestParameters) { - if (!requestParameters.libVersion) { - return this.getErrorReturn(this.CONSTANTS.ERROR_CODES.LIB_VERSION_MISSING); + // Floor + const bidFloor = this.getBidFloor(bidRequest) || getBidIdParameter('bidFloor', bidRequest.params); + if (bidFloor) { + const bidFloorCur = getBidIdParameter('bidFloorCur', bidRequest.params) || 'USD'; + deepSetValue(imp, 'bidfloor', bidFloor); + deepSetValue(imp, 'bidfloorcur', bidFloorCur ? bidFloorCur.toUpperCase() : undefined); } - requestParameters.returnObjType = requestParameters.returnObjType || this.CONSTANTS.RETURN_OBJ_TYPE.DEFAULT; - requestParameters.adServerBaseUrl = 'https://' + (requestParameters.adServerBaseUrl || this.CONSTANTS.AD_SERVER_BASE_URL); - - let impressionObjects = []; - let impressionObject; - if (isArray(requestObject)) { - for (let counter = 0; counter < requestObject.length; counter++) { - impressionObject = this.createImpressionObject(requestObject[counter]); - impressionObjects.push(impressionObject); + const bidderParamsPath = extendMode ? 'ext.prebid.bidder.improvedigital' : 'ext.bidder'; + const placementId = getBidIdParameter('placementId', bidRequest.params); + if (placementId) { + deepSetValue(imp, `${bidderParamsPath}.placementId`, placementId); + if (extendMode) { + deepSetValue(imp, 'ext.prebid.storedrequest.id', '' + placementId); } } else { - impressionObject = this.createImpressionObject(requestObject); - impressionObjects.push(impressionObject); + deepSetValue(imp, `${bidderParamsPath}.publisherId`, getBidIdParameter('publisherId', bidRequest.params)); + deepSetValue(imp, `${bidderParamsPath}.placementKey`, getBidIdParameter('placementKey', bidRequest.params)); } - let returnIdMappings = true; - if (requestParameters.returnObjType === this.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT) { - returnIdMappings = false; - } + deepSetValue(imp, `${bidderParamsPath}.keyValues`, getBidIdParameter('keyValues', bidRequest.params) || undefined); - let returnObject = {}; - returnObject.requests = []; - if (returnIdMappings) { - returnObject.idMappings = []; + // Adding GPID + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || + deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot') || + deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.adslot'); + + deepSetValue(imp, 'ext.gpid', gpid); + + // Adding Interstitial Signal + if (deepAccess(bidRequest, 'ortb2Imp.instl')) { + imp.instl = 1; } - let errors = null; - let baseUrl = `${requestParameters.adServerBaseUrl}/${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; + const videoParams = deepAccess(bidRequest, 'mediaTypes.video'); + if (videoParams) { + imp.video = this.buildVideoRequest(bidRequest); + deepSetValue(imp, 'ext.is_rewarded_inventory', (videoParams.rewarded === 1 || deepAccess(videoParams, 'ext.rewarded') === 1) || undefined); + } - let bidRequestObject = { - bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) - }; - for (let counter = 0; counter < impressionObjects.length; counter++) { - impressionObject = impressionObjects[counter]; - - if (impressionObject.errorCode) { - errors = errors || []; - errors.push({ - errorCode: impressionObject.errorCode, - adUnitId: impressionObject.adUnitId - }); - } else { - if (returnIdMappings) { - returnObject.idMappings.push({ - adUnitId: impressionObject.adUnitId, - id: impressionObject.impressionObject.id - }); - } - bidRequestObject.bid_request.imp = bidRequestObject.bid_request.imp || []; - bidRequestObject.bid_request.imp.push(impressionObject.impressionObject); - - let writeLongRequest = false; - const outputUri = baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)); - if (outputUri.length > this.CONSTANTS.MAX_URL_LENGTH) { - writeLongRequest = true; - if (bidRequestObject.bid_request.imp.length > 1) { - // Pop the current request and process it again in the next iteration - bidRequestObject.bid_request.imp.pop(); - if (returnIdMappings) { - returnObject.idMappings.pop(); - } - counter--; - } - } + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + imp.banner = this.buildBannerRequest(bidRequest); + } - if (writeLongRequest || - !requestParameters.singleRequestMode || - counter === impressionObjects.length - 1) { - returnObject.requests.push(this.formatRequest(requestParameters, bidRequestObject)); - bidRequestObject = { - bid_request: this.createBasicBidRequestObject(requestParameters, extraRequestParameters) - }; - } + if (deepAccess(bidRequest, 'mediaTypes.native')) { + const nativeImp = this.buildNativeRequest(bidRequest); + if (nativeImp) { + imp.native = nativeImp; } } - if (errors) { - returnObject.errors = errors; - } + return imp; + }, - return returnObject; - }; + buildVideoRequest(bidRequest) { + const videoParams = deepClone(bidRequest.mediaTypes.video); + const videoImproveParams = deepClone(deepAccess(bidRequest, 'params.video', {})); + const video = {...videoParams, ...videoImproveParams}; - this.formatRequest = function(requestParameters, bidRequestObject) { - switch (requestParameters.returnObjType) { - case this.CONSTANTS.RETURN_OBJ_TYPE.URL_PARAMS_SPLIT: - return { - method: 'GET', - url: `${requestParameters.adServerBaseUrl}/${this.CONSTANTS.END_POINT}`, - data: `${this.CONSTANTS.AD_SERVER_URL_PARAM}${encodeURIComponent(JSON.stringify(bidRequestObject))}` - }; - default: - const baseUrl = `${requestParameters.adServerBaseUrl}/` + - `${this.CONSTANTS.END_POINT}?${this.CONSTANTS.AD_SERVER_URL_PARAM}`; - return { - url: baseUrl + encodeURIComponent(JSON.stringify(bidRequestObject)) - } + if (Array.isArray(video.playerSize)) { + // Player size can be defined as [w, h] or [[w, h]] + const size = Array.isArray(video.playerSize[0]) ? video.playerSize[0] : video.playerSize; + video.w = size[0]; + video.h = size[1]; } - }; + video.placement = this.isOutstreamVideo(bidRequest) ? VIDEO_PARAMS.PLACEMENT_TYPE.OUTSTREAM : VIDEO_PARAMS.PLACEMENT_TYPE.INSTREAM; - this.createBasicBidRequestObject = function(requestParameters, extraRequestParameters) { - let impressionBidRequestObject = {}; - impressionBidRequestObject.secure = 1; - if (requestParameters.requestId) { - impressionBidRequestObject.id = requestParameters.requestId; - } else { - impressionBidRequestObject.id = getUniqueIdentifierStr(); - } - if (requestParameters.domain) { - impressionBidRequestObject.domain = requestParameters.domain; - } - if (requestParameters.page) { - impressionBidRequestObject.page = requestParameters.page; - } - if (requestParameters.ref) { - impressionBidRequestObject.ref = requestParameters.ref; - } - if (requestParameters.callback) { - impressionBidRequestObject.callback = requestParameters.callback; - } - if (requestParameters.libVersion) { - impressionBidRequestObject.version = requestParameters.libVersion + '-' + this.CONSTANTS.CLIENT_VERSION; - } - if (requestParameters.referrer) { - impressionBidRequestObject.referrer = requestParameters.referrer; - } - if (requestParameters.gdpr || requestParameters.gdpr === 0) { - impressionBidRequestObject.gdpr = requestParameters.gdpr; - } - if (requestParameters.usPrivacy) { - impressionBidRequestObject.us_privacy = requestParameters.usPrivacy; - } - if (requestParameters.schain) { - impressionBidRequestObject.schain = requestParameters.schain; - } - if (requestParameters.pagecat) { - impressionBidRequestObject.pagecat = requestParameters.pagecat; - } - if (requestParameters.genre) { - impressionBidRequestObject.genre = requestParameters.genre; - } - if (requestParameters.user) { - impressionBidRequestObject.user = requestParameters.user; - } - if (extraRequestParameters) { - for (let prop in extraRequestParameters) { - impressionBidRequestObject[prop] = extraRequestParameters[prop]; - } + // Mimes is required + if (!video.mimes) { + video.mimes = VIDEO_PARAMS.DEFAULT_MIMES; } - if (requestParameters.coppa) { - impressionBidRequestObject.coppa = 1; + // skip must be 0 or 1 + if (video.skip !== 1) { + delete video.skipmin; + delete video.skipafter; + if (video.skip !== 0) { + logWarn(`video.skip: invalid value '${video.skip}'. Expected 0 or 1`); + delete video.skip; + } } - return impressionBidRequestObject; - }; + Object.keys(video).forEach(prop => { + if (VIDEO_PARAMS.SUPPORTED_PROPERTIES.indexOf(prop) === -1) delete video[prop]; + }); + return video; + }, - this.createImpressionObject = function(placementObject) { - let outputObject = {}; - let impressionObject = {}; - outputObject.impressionObject = impressionObject; + buildBannerRequest(bidRequest) { + // Set the desired creative sizes + // Input Format: array of pairs, i.e. [[300, 250], [250, 250]] + // Unless improvedigital.usePrebidSizes == true, no sizes are sent to the server + // and the sizes defined in the server for the placement will be used + const banner = {}; + if (config.getConfig('improvedigital.usePrebidSizes') === true && bidRequest.sizes) { + // Convert sizes from [x, y] to { w: x, h: y} + banner.format = bidRequest.sizes.map(sizePair => ({w: sizePair[0], h: sizePair[1]})); + } + return banner; + }, - if (placementObject.id) { - impressionObject.id = placementObject.id; - } else { - impressionObject.id = getUniqueIdentifierStr(); - } - if (placementObject.adTypes) { - impressionObject.ad_types = placementObject.adTypes; - } - if (placementObject.adUnitId) { - outputObject.adUnitId = placementObject.adUnitId; - } - if (placementObject.currency) { - impressionObject.currency = placementObject.currency.toUpperCase(); - } - if (placementObject.bidFloor) { - impressionObject.bidfloor = placementObject.bidFloor; - } - if (placementObject.bidFloorCur) { - impressionObject.bidfloorcur = placementObject.bidFloorCur.toUpperCase(); - } - if (placementObject.placementId) { - impressionObject.pid = placementObject.placementId; + buildNativeRequest(bidRequest) { + const nativeParams = bidRequest.nativeParams; + if (!nativeParams) { + return null; + } + const request = { + assets: [], + } + for (let i of Object.keys(nativeParams)) { + const assetOrtbParams = NATIVE_DATA.ASSETS[i]; + if (assetOrtbParams) { + const assetParams = nativeParams[i]; + const asset = { + id: assetOrtbParams.id, + required: Number(assetParams.required), + }; + switch (assetOrtbParams.assetType) { + case NATIVE_DATA.ASSET_TYPES.TITLE: + asset.title = {len: assetParams.len || assetOrtbParams.default.len}; + break; + case NATIVE_DATA.ASSET_TYPES.DATA: + asset.data = cleanObj({type: assetOrtbParams.type, len: assetParams.len}) + break; + case NATIVE_DATA.ASSET_TYPES.IMG: + asset.img = cleanObj({ + type: assetOrtbParams.type, + w: deepAccess(assetParams, 'sizes.0'), + h: deepAccess(assetParams, 'sizes.1'), + wmin: deepAccess(assetParams, 'aspect_ratios.0.min_width'), + hmin: deepAccess(assetParams, 'aspect_ratios.0.min_height') + }); + break; + default: + return; + } + request.assets.push(asset); + } } - if (placementObject.publisherId) { - impressionObject.pubid = placementObject.publisherId; + if (!request.assets.length) { + logWarn('No native assets recognized. Ignoring native ad request'); + return null; } - if (placementObject.placementKey) { - impressionObject.pkey = placementObject.placementKey; + return { ver: NATIVE_DATA.VERSION, request: JSON.stringify(request) }; + }, + + isOutstreamVideo(bidRequest) { + return deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream'; + }, + + getBidFloor(bidRequest) { + if (!isFn(bidRequest.getFloor)) { + return null; } - if (placementObject.transactionId) { - impressionObject.tid = placementObject.transactionId; + const floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; } - if (!isEmpty(placementObject.video)) { - const video = Object.assign({}, placementObject.video); - // skip must be 0 or 1 - if (video.skip !== 1) { - delete video.skipmin; - delete video.skipafter; - if (video.skip !== 0) { - logWarn(`video.skip: invalid value '${video.skip}'. Expected 0 or 1`); - delete video.skip; - } - } - if (!isEmpty(video)) { - impressionObject.video = video; + return null; + }, + + buildSiteOrApp(request, bidderRequest) { + const app = {}; + const configAppSettings = config.getConfig('app') || {}; + const fpdAppSettings = bidderRequest.ortb2?.app || {}; + mergeDeep(app, configAppSettings, fpdAppSettings); + + if (Object.keys(app).length !== 0) { + request.app = app; + } else { + const site = {}; + const url = deepAccess(bidderRequest, 'refererInfo.page'); + if (url) { + site.page = url; + site.domain = bidderRequest.refererInfo.domain } + const configSiteSettings = config.getConfig('site') || {}; + const fpdSiteSettings = deepAccess(bidderRequest, 'ortb2.site') || {}; + mergeDeep(site, configSiteSettings, fpdSiteSettings); + request.site = site; } - if (placementObject.keyValues) { - for (let key in placementObject.keyValues) { - for (let valueCounter = 0; valueCounter < placementObject.keyValues[key].length; valueCounter++) { - impressionObject.kvw = impressionObject.kvw || {}; - impressionObject.kvw[key] = impressionObject.kvw[key] || []; - impressionObject.kvw[key].push(placementObject.keyValues[key][valueCounter]); - } + }, +}; + +const ID_RESPONSE = { + buildAd(bid, bidRequest, bidResponse) { + if (bidRequest.mediaTypes && Object.keys(bidRequest.mediaTypes).length === 1) { + if (deepAccess(bidRequest, 'mediaTypes.video')) { + this.buildVideoAd(bid, bidRequest, bidResponse); + } else if (deepAccess(bidRequest, 'mediaTypes.banner')) { + this.buildBannerAd(bid, bidRequest, bidResponse); + } else if (deepAccess(bidRequest, 'mediaTypes.native')) { + this.buildNativeAd(bid, bidRequest, bidResponse) + } + } else { + // Detect media type for multi-format response + if (bidResponse.adm.search(/^(<\?xml| sizePair.length === 2 && - isInteger(sizePair[0]) && - isInteger(sizePair[1]) && - sizePair[0] >= 0 && - sizePair[1] >= 0) - .map(sizePair => { - return { w: sizePair[0], h: sizePair[1] } - }); - if (format.length > 0) { - impressionObject.banner.format = format; + buildBannerAd(bid, bidRequest, bidResponse) { + bid.mediaType = BANNER; + bid.ad = bidResponse.adm; + bid.width = bidResponse.w; + bid.height = bidResponse.h; + }, + + buildNativeAd(bid, bidRequest, bidResponse) { + bid.mediaType = NATIVE; + const nativeResponse = JSON.parse(bidResponse.adm); + const nativeAd = { + clickUrl: deepAccess(nativeResponse, 'link.url'), + clickTrackers: deepAccess(nativeResponse, 'link.clicktrackers'), + privacyLink: nativeResponse.privacy + } + // Trackers + if (nativeResponse.eventtrackers) { + nativeAd.impressionTrackers = []; + nativeResponse.eventtrackers.forEach(tracker => { + // Only handle impression event. Viewability events are not supported yet. + if (tracker.event !== 1) return; + switch (tracker.method) { + case 1: // img + nativeAd.impressionTrackers.push(tracker.url); + break; + case 2: // js + // javascriptTrackers is a string. If there's more than one JS tracker in bid response, the last script will be used. + nativeAd.javascriptTrackers = ``; + break; + } + }); + } else { + nativeAd.impressionTrackers = nativeResponse.imptrackers || []; + nativeAd.javascriptTrackers = nativeResponse.jstracker; + } + nativeResponse.assets.map(asset => { + const assetParams = NATIVE_DATA.getAssetById(asset.id); + switch (assetParams.assetType) { + case NATIVE_DATA.ASSET_TYPES.TITLE: + nativeAd.title = asset.title.text; + break; + case NATIVE_DATA.ASSET_TYPES.DATA: + nativeAd[assetParams.name] = asset.data.value; + break; + case NATIVE_DATA.ASSET_TYPES.IMG: + nativeAd[assetParams.name] = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h, + }; + break; } + }); + bid.native = nativeAd; + }, +}; + +const ID_OUTSTREAM = { + RENDERER_URL: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js', + createRenderer(bidRequest) { + const renderer = Renderer.install({ + id: bidRequest.adUnitCode, + url: this.RENDERER_URL, + config: deepAccess(bidRequest, 'renderer.options'), + adUnitCode: bidRequest.adUnitCode + }); + try { + renderer.setRender(this.render); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); } + return renderer; + }, + + render(bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, ID_OUTSTREAM.handleRendererEvents.bind(null, bid)); + }); + }, + + handleRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); + }, +}; - if (!impressionObject.pid && - !impressionObject.pubid && - !impressionObject.pkey && - !(impressionObject.banner && impressionObject.banner.w && impressionObject.banner.h)) { - outputObject.impressionObject = null; - outputObject.errorCode = this.CONSTANTS.ERROR_CODES.MISSING_PLACEMENT_PARAMS; +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); } - return outputObject; - }; -} + }, + + isValidBid(bid) { + return bid && /razr:\/\//.test(bid.ad); + }, + + render(bid) { + const {bidRequest} = bid.renderer.getConfig(); + + const payload = { + type: 'prebid', + bidRequest, + bid, + config: mergeDeep( + {}, + config.getConfig('improvedigital.rendererConfig'), + deepAccess(bidRequest, 'params.rendererConfig') + ) + }; + + const razr = window.razr = window.razr || {}; + razr.queue = razr.queue || []; + razr.queue.push(payload); + } +}; diff --git a/modules/incrxBidAdapter.js b/modules/incrxBidAdapter.js new file mode 100644 index 00000000000..914ef0b904e --- /dev/null +++ b/modules/incrxBidAdapter.js @@ -0,0 +1,88 @@ +import { parseSizesInput, isEmpty } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js' + +const BIDDER_CODE = 'incrementx'; +const ENDPOINT_URL = 'https://hb.incrementxserv.com/vzhbidder/bid'; +const DEFAULT_CURRENCY = 'USD'; +const CREATIVE_TTL = 300; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * 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.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param validBidRequests + * @param bidderRequest + * @return Array Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map(bidRequest => { + const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes); + + const requestParams = { + _vzPlacementId: bidRequest.params.placementId, + sizes: sizes, + _slotBidId: bidRequest.bidId, + // TODO: is 'page' the right value here? + _rqsrc: bidderRequest.refererInfo.page, + }; + + const payload = { + q: encodeURI(JSON.stringify(requestParams)) + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload + }; + }); + }, + + /** + * 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) { + const response = serverResponse.body; + const bids = []; + if (isEmpty(response)) { + return bids; + } + const responseBid = { + requestId: response.slotBidId, + cpm: response.cpm, + currency: response.currency || DEFAULT_CURRENCY, + width: response.adWidth, + height: response.adHeight, + ttl: CREATIVE_TTL, + creativeId: response.creativeId || 0, + netRevenue: response.netRevenue || false, + meta: { + mediaType: response.mediaType || BANNER, + advertiserDomains: response.advertiserDomains || [] + }, + ad: response.ad + }; + bids.push(responseBid); + return bids; + } + +}; + +registerBidder(spec); 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 781bca90660..b76a759a047 100644 --- a/modules/inskinBidAdapter.js +++ b/modules/inskinBidAdapter.js @@ -1,4 +1,5 @@ import { createTrackPixelHtml } from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'inskin'; @@ -49,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 @@ -82,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); + } + }); } } @@ -211,9 +210,9 @@ export const spec = { bidPrice: bidsMap[e.data.bidId].price, serverResponse }; - const script = document.createElement('script'); - script.src = 'https://cdn.inskinad.com/isfe/publishercode/' + bidsMap[e.data.bidId].params.siteId + '/default.js?autoload&id=' + id; - document.getElementsByTagName('head')[0].appendChild(script); + + const url = 'https://cdn.inskinad.com/isfe/publishercode/' + bidsMap[e.data.bidId].params.siteId + '/default.js?autoload&id=' + id; + loadExternalScript(url, BIDDER_CODE); }); } diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 4122166a151..71815f058e7 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -1,5 +1,5 @@ import {config} from '../src/config.js'; -import {BANNER} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {deepAccess, generateUUID, logError, isArray} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -48,14 +48,9 @@ function setUserId(userId) { } } -function buildImpression(bidRequest) { +function buildBanner(bidRequest) { const format = []; - const ext = { - insticator: { - adUnitId: bidRequest.params.adUnitId, - }, - } - + const pos = deepAccess(bidRequest, 'mediaTypes.banner.pos'); const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; @@ -66,23 +61,53 @@ function buildImpression(bidRequest) { }); } - const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); - - if (gpid) { - ext.gpid = gpid; + return { + format, + pos, } +} + +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, + } +} + +function buildImpression(bidRequest) { + const imp = { id: bidRequest.bidId, tagid: bidRequest.adUnitCode, - banner: { - format, + instl: deepAccess(bidRequest, 'ortb2Imp.instl'), + secure: location.protocol === 'https:' ? 1 : 0, + ext: { + gpid: deepAccess(bidRequest, 'ortb2Imp.ext.gpid'), + insticator: { + adUnitId: bidRequest.params.adUnitId, + }, }, - ext, - }; + } + + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + imp.banner = buildBanner(bidRequest); + } + + if (deepAccess(bidRequest, 'mediaTypes.video')) { + imp.video = buildVideo(bidRequest); + } + + return imp; } function buildDevice() { + const deviceConfig = config.getConfig('device'); const device = { w: window.innerWidth, h: window.innerHeight, @@ -93,8 +118,6 @@ function buildDevice() { }, }; - const deviceConfig = config.getConfig('device'); - if (typeof deviceConfig === 'object') { Object.assign(device, deviceConfig); } @@ -115,13 +138,17 @@ function buildRegs(bidderRequest) { return {}; } -function buildUser() { +function buildUser(bid) { const userId = getUserId() || generateUUID(); + const yob = deepAccess(bid, 'params.user.yob') + const gender = deepAccess(bid, 'params.user.gender') setUserId(userId); return { id: userId, + yob, + gender, }; } @@ -152,13 +179,14 @@ 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), - user: buildUser(), + user: buildUser(validBidRequests[0]), imp: validBidRequests.map((bidRequest) => buildImpression(bidRequest)), ext: { insticator: { @@ -242,31 +270,90 @@ function validateSizes(sizes) { ); } -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - supportedMediaTypes: [BANNER], +function validateAdUnitId(bid) { + if (!bid.params.adUnitId) { + logError('insticator: missing adUnitId bid parameter'); + return false; + } - isBidRequestValid: function (bid) { - if (!bid.params.adUnitId) { - logError('insticator: missing adUnitId bid parameter'); - return false; - } + return true; +} - if (!(BANNER in bid.mediaTypes)) { - logError('insticator: expected banner in mediaTypes'); - return false; - } +function validateMediaType(bid) { + if (!(BANNER in bid.mediaTypes || VIDEO in bid.mediaTypes)) { + logError('insticator: expected banner or video in mediaTypes'); + return false; + } - if ( - !validateSizes(bid.sizes) && - !validateSizes(bid.mediaTypes.banner.sizes) - ) { - logError('insticator: banner sizes not specified or invalid'); - return false; - } + return true; +} + +function validateBanner(bid) { + const banner = deepAccess(bid, 'mediaTypes.banner'); + + if (banner === undefined) { + return true; + } + if ( + !validateSizes(bid.sizes) && + !validateSizes(bid.mediaTypes.banner.sizes) + ) { + logError('insticator: banner sizes not specified or invalid'); + return false; + } + + return true; +} + +function validateVideo(bid) { + const video = deepAccess(bid, 'mediaTypes.video'); + + if (video === undefined) { return true; + } + + const videoSize = [ + deepAccess(bid, 'mediaTypes.video.w'), + deepAccess(bid, 'mediaTypes.video.h'), + ]; + + if ( + !validateSize(videoSize) + ) { + logError('insticator: video size not specified or invalid'); + return false; + } + + const mimes = deepAccess(bid, 'mediaTypes.video.mimes'); + + if (!Array.isArray(mimes) || mimes.length === 0) { + logError('insticator: mimes not specified'); + 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; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [ BANNER, VIDEO ], + + isBidRequestValid: function (bid) { + return ( + validateAdUnitId(bid) && + validateMediaType(bid) && + validateBanner(bid) && + validateVideo(bid) + ); }, buildRequests: function (validBidRequests, bidderRequest) { diff --git a/modules/integr8BidAdapter.js b/modules/integr8BidAdapter.js index d61fe624c59..3ba68ffb6d6 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; } } diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index d60ab3962ae..1347fa04bd5 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -6,16 +6,17 @@ */ import { logError, logInfo } from '../src/utils.js'; -import {ajax} from '../src/ajax.js'; -import {submodule} from '../src/hook.js' -import {getStorageManager} from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js' +import { getStorageManager } from '../src/storageManager.js'; const PCID_EXPIRY = 365; const MODULE_NAME = 'intentIqId'; export const FIRST_PARTY_KEY = '_iiq_fdata'; +export var FIRST_PARTY_DATA_KEY = '_iiq_fdata'; -export const storage = getStorageManager({gvlid: undefined, moduleName: MODULE_NAME}); +export const storage = getStorageManager({ gvlid: undefined, moduleName: MODULE_NAME }); const INVALID_ID = 'INVALID_ID'; @@ -117,6 +118,8 @@ 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 } + let rrttStrtTime = 0; // Read Intent IQ 1st party id or generate it if none exists let firstPartyData = tryParse(readData(FIRST_PARTY_KEY)); @@ -126,12 +129,17 @@ export const intentIqIdSubmodule = { storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); } + let partnerData = tryParse(readData(FIRST_PARTY_DATA_KEY)); + if (!partnerData) partnerData = {}; + // use protocol relative urls for http or https let url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&dpi=${configParams.partner}&pt=17&dpn=1`; url += configParams.pcid ? '&pcid=' + encodeURIComponent(configParams.pcid) : ''; url += configParams.pai ? '&pai=' + encodeURIComponent(configParams.pai) : ''; url += firstPartyData.pcid ? '&iiqidtype=2&iiqpcid=' + encodeURIComponent(firstPartyData.pcid) : ''; url += firstPartyData.pid ? '&pid=' + encodeURIComponent(firstPartyData.pid) : ''; + url += (partnerData.cttl) ? '&cttl=' + encodeURIComponent(partnerData.cttl) : ''; + url += (partnerData.rrtt) ? '&rrtt=' + encodeURIComponent(partnerData.rrtt) : ''; const resp = function (callback) { const callbacks = { @@ -140,14 +148,30 @@ export const intentIqIdSubmodule = { // If response is a valid json and should save is true if (respJson && respJson.ls) { // Store pid field if found in response json + let shouldUpdateLs = false; if ('pid' in respJson) { firstPartyData.pid = respJson.pid; - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); + shouldUpdateLs = true; + } + if ('cttl' in respJson) { + partnerData.cttl = respJson.cttl; + shouldUpdateLs = true; } - // If should save and data is empty, means we should save as INVALID_ID if (respJson.data == '') { respJson.data = INVALID_ID; + } else { + partnerData.data = respJson.data; + shouldUpdateLs = true; + } + if (rrttStrtTime && rrttStrtTime > 0) { + partnerData.rrtt = Date.now() - rrttStrtTime; + shouldUpdateLs = true; + } + if (shouldUpdateLs === true) { + partnerData.date = Date.now() + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData)); + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData)); } callback(respJson.data); } else { @@ -159,9 +183,13 @@ export const intentIqIdSubmodule = { callback(); } }; - ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); + if (partnerData.date && partnerData.cttl && partnerData.data && + Date.now() - partnerData.date < partnerData.cttl) { callback(partnerData.data); } else { + rrttStrtTime = Date.now(); + ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); + } }; - return {callback: resp}; + return { callback: resp }; } }; diff --git a/modules/interactiveOffersBidAdapter.js b/modules/interactiveOffersBidAdapter.js index d8a106623fd..25dcd4f3cd1 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; diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index e83786f3857..717a886a1f6 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -9,7 +9,7 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 7, + PREBID_VERSION: 8, METHOD: 'GET', INVIBES_VENDOR_ID: 436, USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'], @@ -95,8 +95,6 @@ function buildRequest(bidRequests, bidderRequest) { invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent); invibes.visitId = invibes.visitId || generateRandomId(); - invibes.noCookies = invibes.noCookies || invibes.getCookie('ivNoCookie'); - let lid = initDomainId(invibes.domainOptions); const currentQueryStringParams = parseQueryStringParams(); let userIdModel = getUserIds(_userId); @@ -113,7 +111,7 @@ function buildRequest(bidRequests, bidderRequest) { location: getDocumentLocation(topWin), videoAdHtmlId: generateRandomId(), showFallback: currentQueryStringParams['advs'] === '0', - ivbsCampIdsLocal: invibes.getCookie('IvbsCampIdsLocal'), + ivbsCampIdsLocal: readFromLocalStorage('IvbsCampIdsLocal'), bidParamsJson: JSON.stringify(bidParamsJson), capCounts: getCappedCampaignsAsString(), @@ -129,9 +127,21 @@ function buildRequest(bidRequests, bidderRequest) { purposes: invibes.purposes.toString(), li: invibes.legitimateInterests.toString(), - tc: invibes.gdpr_consent + tc: invibes.gdpr_consent, + isLocalStorageEnabled: storage.hasLocalStorage(), }; + let lid = readFromLocalStorage('ivbsdid'); + if (!lid) { + let str = invibes.getCookie('ivbsdid'); + if (str) { + try { + let cookieLid = JSON.parse(str); + lid = cookieLid.id ? cookieLid.id : cookieLid; + } catch (e) { + } + } + } if (lid) { data.lId = lid; } @@ -172,6 +182,15 @@ function handleResponse(responseObj, bidRequests) { responseObj = responseObj.body || responseObj; responseObj = responseObj.videoAdContentResult || responseObj; + if (responseObj.ShouldSetLId && responseObj.LId) { + if ((!invibes.optIn || !invibes.purposes[0]) && responseObj.PrivacyPolicyRule && responseObj.TcModel && responseObj.TcModel.PurposeConsents) { + invibes.optIn = responseObj.PrivacyPolicyRule; + invibes.purposes = responseObj.TcModel.PurposeConsents; + } + + setInLocalStorage('ivbsdid', responseObj.LId); + } + if (typeof invibes.bidResponse === 'object') { if (responseObj.MultipositionEnabled === true) { invibes.bidResponse.AdPlacements = invibes.bidResponse.AdPlacements.concat(responseObj.AdPlacements); @@ -411,6 +430,22 @@ function renderCreative(bidModel) { .replace('creativeHtml', bidModel.CreativeHtml); } +function readFromLocalStorage(key) { + if (invibes.GdprModuleInstalled && (!invibes.optIn || !invibes.purposes[0])) { + return; + } + + return storage.getDataFromLocalStorage(key) || ''; +} + +function setInLocalStorage(key, value) { + if (!invibes.optIn || !invibes.purposes[0]) { + return; + } + + storage.setDataInLocalStorage(key, value); +} + function getCappedCampaignsAsString() { const key = 'ivvcap'; @@ -471,14 +506,20 @@ function buildSyncUrl() { syncUrl += '?visitId=' + invibes.visitId; syncUrl += '&optIn=' + invibes.optIn; - const did = invibes.getCookie('ivbsdid'); - if (did) { - syncUrl += '&ivbsdid=' + encodeURIComponent(did); + let did = readFromLocalStorage('ivbsdid'); + if (!did) { + let str = invibes.getCookie('ivbsdid'); + if (str) { + try { + let cookieLid = JSON.parse(str); + did = cookieLid.id ? cookieLid.id : cookieLid; + } catch (e) { + } + } } - const bks = invibes.getCookie('ivvbks'); - if (bks) { - syncUrl += '&ivvbks=' + encodeURIComponent(bks); + if (did) { + syncUrl += '&ivbsdid=' + encodeURIComponent(did); } return syncUrl; @@ -486,6 +527,7 @@ function buildSyncUrl() { function readGdprConsent(gdprConsent) { if (gdprConsent && gdprConsent.vendorData) { + invibes.GdprModuleInstalled = true; invibes.gdpr_consent = getVendorConsentData(gdprConsent.vendorData); if (!gdprConsent.vendorData.gdprApplies || gdprConsent.vendorData.hasGlobalConsent) { @@ -528,6 +570,7 @@ function readGdprConsent(gdprConsent) { return 2; } + invibes.GdprModuleInstalled = false; return 0; } @@ -637,34 +680,13 @@ invibes.getCookie = function (name) { return; } - if (!invibes.optIn || !invibes.purposes[0]) { + if (invibes.GdprModuleInstalled && (!invibes.optIn || !invibes.purposes[0])) { return; } return storage.getCookie(name); }; -let initDomainId = function (options) { - let cookiePersistence = { - cname: 'ivbsdid', - load: function () { - let str = invibes.getCookie(this.cname) || ''; - try { - return JSON.parse(str); - } catch (e) { - } - } - }; - - options = options || {}; - - var persistence = options.persistence || cookiePersistence; - - let state = persistence.load(); - - return state ? (state.id || state.tempId) : undefined; -}; - let keywords = (function () { const cap = 300; let headTag = document.getElementsByTagName('head')[0]; @@ -738,7 +760,6 @@ let keywords = (function () { export function resetInvibes() { invibes.optIn = undefined; - invibes.noCookies = undefined; invibes.dom = undefined; invibes.bidResponse = undefined; invibes.domainOptions = undefined; diff --git a/modules/invisiblyAnalyticsAdapter.js b/modules/invisiblyAnalyticsAdapter.js index 5d15ae55bfc..1f0bbfd46c3 100644 --- a/modules/invisiblyAnalyticsAdapter.js +++ b/modules/invisiblyAnalyticsAdapter.js @@ -6,11 +6,11 @@ import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { generateUUID, logInfo } from '../src/utils.js'; +import CONSTANTS from '../src/constants.json'; const DEFAULT_EVENT_URL = 'https://api.pymx5.com/v1/' + 'sites/events'; const analyticsType = 'endpoint'; const analyticsName = 'Invisibly Analytics Adapter:'; -const CONSTANTS = require('../src/constants.json'); const ajax = ajaxBuilder(0); // Events needed 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..68b027c1bec 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,16 +242,6 @@ 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', {}); diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index 6c0a2e5f56d..3bd613e786b 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -125,7 +125,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 7cec6172ba4..7d3b3a33a7d 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -4,6 +4,7 @@ import { deepAccess, deepClone, deepSetValue, + getGptSlotInfoForAdUnitCode, hasDeviceAccess, inIframe, isArray, @@ -16,12 +17,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 * 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'; @@ -29,22 +30,24 @@ import {Renderer} from '../src/Renderer.js'; const BIDDER_CODE = 'ix'; const ALIAS_BIDDER_CODE = 'roundel'; const GLOBAL_VENDOR_ID = 10; -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 = [144, 144]; const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; -const USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; -const RENDERER_URL = 'https://js-sec.indexww.com/htv/video-player.js'; +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, @@ -73,7 +76,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 }; @@ -86,7 +89,6 @@ const PROVIDERS = [ 'connectid', 'tapadId', 'quantcastId', - 'flocId', 'pubProvidedId' ]; const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration']; // note: protocol/protocols is also reqd @@ -98,9 +100,99 @@ const VIDEO_PARAMS_ALLOW_LIST = [ 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext', 'playerSize', 'w', 'h' ]; +const NATIVE_ASSET_TYPES = { + TITLE: 100, + IMG: 200, + VIDEO: 300, + DATA: 400 +}; +const NATIVE_IMAGE_TYPES = { + ICON: 1, + MAIN: 3 +}; +const NATIVE_DATA_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 +}; +const NATIVE_DATA_MAP = { + [NATIVE_DATA_TYPES.SPONSORED]: 'sponsoredBy', + [NATIVE_DATA_TYPES.DESC]: 'body', + [NATIVE_DATA_TYPES.RATING]: 'rating', + [NATIVE_DATA_TYPES.LIKES]: 'likes', + [NATIVE_DATA_TYPES.DOWNLOADS]: 'downloads', + [NATIVE_DATA_TYPES.PRICE]: 'price', + [NATIVE_DATA_TYPES.SALEPRICE]: 'salePrice', + [NATIVE_DATA_TYPES.PHONE]: 'phone', + [NATIVE_DATA_TYPES.ADDRESS]: 'address', + [NATIVE_DATA_TYPES.DESC2]: 'body2', + [NATIVE_DATA_TYPES.DISPLAYURL]: 'displayUrl', + [NATIVE_DATA_TYPES.CTATEXT]: 'cta' +}; +const NATIVE_ASSETS_MAP = { + 'title': { assetType: NATIVE_ASSET_TYPES.TITLE }, + 'icon': { assetType: NATIVE_ASSET_TYPES.IMG, subtype: NATIVE_IMAGE_TYPES.ICON }, + 'image': { assetType: NATIVE_ASSET_TYPES.IMG, subtype: NATIVE_IMAGE_TYPES.MAIN }, + 'sponsoredBy': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.SPONSORED }, + 'body': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.DESC }, + 'rating': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.RATING }, + 'likes': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.LIKES }, + 'downloads': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.DOWNLOADS }, + 'price': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.PRICE }, + 'salePrice': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.SALEPRICE }, + 'phone': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.PHONE }, + 'address': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.ADDRESS }, + 'body2': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.DESC2 }, + 'displayUrl': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.DISPLAYURL }, + 'cta': { assetType: NATIVE_ASSET_TYPES.DATA, subtype: NATIVE_DATA_TYPES.CTATEXT }, + 'video': { assetType: NATIVE_ASSET_TYPES.VIDEO } +}; +const NATIVE_ALLOWED_PROPERTIES = [ + 'rendererUrl', + 'sendTargetingKeys', + 'adTemplate', + 'type', + 'ext', + 'privacyLink', + 'clickUrl', + 'privacyIcon' +]; +const NATIVE_ASSET_DEFAULT = { + TITLE: { + LEN: 25 + }, + VIDEO: { + MIMES: [ + 'video/mp4', + 'video/webm' + ], + MINDURATION: 0, + MAXDURATION: 120, + PROTOCOLS: [2, 3, 5, 6], + } +}; +const NATIVE_EVENT_TYPES = { + IMRESSION: 1 +}; +const NATIVE_EVENT_TRACKING_METHOD = { + IMG: 1, + JS: 2 +}; 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 = { @@ -108,7 +200,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. @@ -152,6 +244,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) { @@ -175,7 +269,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`); } @@ -191,7 +289,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 {}; } } @@ -201,6 +299,146 @@ 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 nativeAdUnitRef = deepAccess(bid, 'mediaTypes.native'); + + const assets = [] + + // Convert all native assets to imp object + for (const [adUnitProperty, adUnitValues] of Object.entries(nativeAdUnitRef)) { + if (!NATIVE_ASSETS_MAP[adUnitProperty]) { + continue; + } + + const { assetType, subtype } = NATIVE_ASSETS_MAP[adUnitProperty]; + let asset; + switch (assetType) { + case NATIVE_ASSET_TYPES.TITLE: + asset = createNativeTitleRequest(adUnitValues); + break; + case NATIVE_ASSET_TYPES.IMG: + asset = createNativeImgRequest(adUnitValues, subtype); + break; + case NATIVE_ASSET_TYPES.VIDEO: + asset = createNativeVideoRequest(adUnitValues); + break; + case NATIVE_ASSET_TYPES.DATA: + asset = createNativeDataRequest(adUnitValues, subtype); + break; + } + asset.id = assetType + (subtype || 0); + assets.push(asset); + } + + if (assets.length === 0) { + logWarn('IX Bid Adapter: Native bid does not contain recognised assets in [mediaTypes.native]'); + return {}; + } + + const request = { + assets: assets, + ver: '1.2', + eventtrackers: [{ + event: 1, + methods: [1, 2] + }], + 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 native bid asset to a native impression asset + * @param {object} bidAsset PBJS bid asset object + * @returns {object} IX impression asset object + */ +function createNativeTitleRequest(bidAsset) { + return { + required: bidAsset.required ? 1 : 0, + title: { + len: bidAsset.len ? bidAsset.len : NATIVE_ASSET_DEFAULT.TITLE.LEN, + ext: bidAsset.ext + } + } +} + +/** + * Converts native bid asset to a native impression asset + * @param {object} bidAsset PBJS bid asset object + * @param {int} type The image type + * @returns {object} IX impression asset object + */ +function createNativeImgRequest(bidAsset, type) { + let asset = { + required: bidAsset.required ? 1 : 0, + img: { + type: type, + mimes: bidAsset.mimes, + ext: bidAsset.ext + } + } + + if (bidAsset.hasOwnProperty('sizes') && bidAsset.sizes.length === 2) { + asset.img.wmin = bidAsset.sizes[0]; + asset.img.hmin = bidAsset.sizes[1]; + } + + return asset +} + +/** + * Converts native bid asset to a native impression asset + * @param {object} bidAsset PBJS bid asset object + * @returns {object} IX impression asset object + */ +function createNativeVideoRequest(bidAsset) { + return { + required: bidAsset.required ? 1 : 0, + video: { + mimes: bidAsset.mimes ? bidAsset.mimes : NATIVE_ASSET_DEFAULT.VIDEO.MIMES, + minduration: bidAsset.minduration ? bidAsset.minduration : NATIVE_ASSET_DEFAULT.VIDEO.MINDURATION, + maxduration: bidAsset.maxduration ? bidAsset.maxduration : NATIVE_ASSET_DEFAULT.VIDEO.MAXDURATION, + protocols: bidAsset.protocols ? bidAsset.protocols : NATIVE_ASSET_DEFAULT.VIDEO.PROTOCOLS, + ext: bidAsset.ext + } + } +} + +/** + * Converts native bid asset to a native impression asset + * @param {object} bidAsset PBJS bid asset object + * @param {int} type The image type + * @returns {object} IX impression asset object + */ +function createNativeDataRequest(bidAsset, type) { + return { + required: bidAsset.required ? 1 : 0, + data: { + type: type, + len: bidAsset.len, + ext: bidAsset.ext + } + } +} + /** * Converts an incoming PBJS bid to an IX Impression * @param {object} bid PBJS bid object @@ -303,6 +541,16 @@ function parseBid(rawBid, currency, bidRequest) { 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 if ((rawBid.ext && rawBid.ext.vasturl) || rawBid.mtype == MEDIA_TYPES.Video) { bid.width = bidRequest.video.w; @@ -310,6 +558,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 = interpretNativeAdm(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; @@ -329,6 +583,84 @@ function parseBid(rawBid, currency, bidRequest) { return bid; } +/** + * Parse native adm and set native asset key names recognized by Prebid.js + * @param {string} adm Native adm complience + */ +function interpretNativeAdm(nativeResponse) { + const native = { + clickUrl: nativeResponse.link.url, + privacyLink: nativeResponse.privacy + }; + + for (const asset of nativeResponse.assets) { + const subtype = asset.id % 100; + const assetType = asset.id - subtype; + + switch (assetType) { + case NATIVE_ASSET_TYPES.TITLE: + native.title = asset.title && asset.title.text; + break; + case NATIVE_ASSET_TYPES.IMG: + const image = { + url: asset.img && asset.img.url, + height: asset.img && asset.img.h, + width: asset.img && asset.img.w + }; + native[subtype === NATIVE_IMAGE_TYPES.ICON ? 'icon' : 'image'] = image; + break; + case NATIVE_ASSET_TYPES.VIDEO: + native.video = asset.video && asset.video.vasttag; + break; + case NATIVE_ASSET_TYPES.DATA: + setDataAsset(native, asset, subtype); + break; + default: + logWarn(`IX Bid Adapter: native asset ID ${asset.id} could not be recognized`); + } + } + + setTrackers(native, nativeResponse); + return native; +} + +function setDataAsset(native, asset, type) { + if (!(type in NATIVE_DATA_MAP)) { + logWarn(`IX Bid Adapter: native data asset type ${type} is not supported`); + return; + } + native[NATIVE_DATA_MAP[type]] = asset.data && asset.data.value; +} + +function setTrackers(native, nativeResponse) { + native.impressionTrackers = [] + + if (Array.isArray(nativeResponse.imptrackers)) { + native.impressionTrackers.push(...nativeResponse.imptrackers) + } + + if (Array.isArray(nativeResponse.link.clicktrackers)) { + native.impressionTrackers.push(...nativeResponse.link.clicktrackers) + } + + if (Array.isArray(nativeResponse.eventtrackers)) { + nativeResponse.eventtrackers.forEach(tracker => { + if (tracker.event !== NATIVE_EVENT_TYPES.IMRESSION) { + return + } + + switch (tracker.method) { + case NATIVE_EVENT_TRACKING_METHOD.IMG: + native.impressionTrackers.push(tracker.url); + break; + case NATIVE_EVENT_TRACKING_METHOD.JS: + native.javascriptTrackers = ``; + break; + } + }) + } +} + /** * Determines whether or not the given object is valid size format. * @@ -426,6 +758,27 @@ function isValidBidFloorParams(bidFloor, bidFloorCur) { bidFloorCur.match(curRegex)); } +function nativeMediaTypeValid(nativeObj) { + if (nativeObj === undefined) { + return true; + } + + let hasValidAsset = false; + + for (const property in nativeObj) { + if (!(property in NATIVE_ASSETS_MAP) && !NATIVE_ALLOWED_PROPERTIES.includes(property)) { + logError('IX Bid Adapter: native', { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); + return false; + } + + if (property in NATIVE_ASSETS_MAP) { + hasValidAsset = true; + } + } + + return hasValidAsset; +} + /** * Get bid request object with the associated id. * @@ -449,11 +802,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)) { @@ -471,16 +823,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 }; } @@ -490,7 +832,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. * */ @@ -498,9 +840,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 @@ -525,6 +867,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(); @@ -543,6 +886,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(); @@ -571,7 +922,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 = { @@ -597,6 +948,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (bidderRequest.uspConsent) { deepSetValue(r, 'regs.ext.us_privacy', bidderRequest.uspConsent); + usPrivacy = bidderRequest.uspConsent; } if (pageUrl) { @@ -610,8 +962,12 @@ 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; + siteID = validBidRequests[0].params.siteId; + payload.s = siteID; payload.v = version; + if (version) { + payload.v = version; + } payload.ac = 'j'; payload.sd = 1; @@ -697,8 +1053,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 = { @@ -709,10 +1071,11 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { }, } - 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]) { @@ -734,7 +1097,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; @@ -773,6 +1136,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) { @@ -834,7 +1212,8 @@ function buildIXDiag(validBidRequests) { allu: 0, ren: false, version: '$prebid.version$', - userIds: _getUserIds(validBidRequests[0]) + userIds: _getUserIds(validBidRequests[0]), + url: window.location.href.split('?')[0] }; // create ad unit map and collect the required diag properties @@ -856,12 +1235,10 @@ function buildIXDiag(validBidRequests) { if (deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { ixdiag.ou++; - // renderer only needed for outstream - const hasRenderer = typeof (deepAccess(bid, 'renderer') || deepAccess(bid, 'mediaTypes.video.renderer')) === 'object'; - - // if any one ad unit is missing renderer, set ren status to false in diag - ixdiag.ren = ixdiag.ren && hasRenderer ? (deepAccess(ixdiag, 'ren')) : hasRenderer; + if (isIndexRendererPreferred(bid)) { + ixdiag.ren = true; + } } if (deepAccess(bid, 'mediaTypes.video.context') === 'instream') { @@ -894,6 +1271,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 @@ -906,6 +1307,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; } } @@ -932,6 +1341,14 @@ 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'); + + 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) { @@ -946,33 +1363,6 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { } } -/** - * 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, 'params.video') && deepAccess(validBidRequest, 'mediaTypes.video')) { - return VIDEO; - } - - return BANNER; -} - /** * Updates the Object to track missing banner sizes. * @@ -1128,24 +1518,18 @@ function getCachedErrors() { /** * - * Initialize Outstream Renderer + * Initialize IX Outstream Renderer * @param {Object} bid */ function outstreamRenderer(bid) { - bid.renderer.push(() => { - var config = { - width: bid.width, - height: bid.height, - timeout: 3000 - }; - - // IXOutstreamPlayer supports both vastUrl and vastXml, so we can pass either. - // Since vastUrl is going to be deprecated from exchange response, vastXml takes priority. - if (bid.vastXml) { - window.IXOutstreamPlayer(bid.vastXml, bid.adUnitCode, config); - } else { - window.IXOutstreamPlayer(bid.vastUrl, bid.adUnitCode, config); + bid.renderer.push(function () { + const adUnitCode = bid.adUnitCode; + const divId = document.getElementById(adUnitCode) ? adUnitCode : getGptSlotInfoForAdUnitCode(adUnitCode).divId; + if (!divId) { + logWarn(`IX Bid Adapter: adUnitCode: ${divId} not found on page.`); + return; } + window.createIXPlayer(divId, bid); }); } @@ -1154,10 +1538,10 @@ function outstreamRenderer(bid) { * @param {string} id * @returns {Renderer} */ -function createRenderer(id) { +function createRenderer(id, renderUrl) { const renderer = Renderer.install({ id: id, - url: RENDERER_URL, + url: renderUrl, loaded: false }); @@ -1165,11 +1549,38 @@ function createRenderer(id) { renderer.setRender(outstreamRenderer); } catch (err) { logWarn('Prebid Error calling setRender on renderer', err); + return null; + } + + if (!renderUrl) { + logWarn('Outstream renderer URL not found'); + return null; } return renderer; } +/** + * Returns whether our renderer could potentially be used. + * @param {*} bid bid object + */ +function isIndexRendererPreferred(bid) { + if (deepAccess(bid, 'mediaTypes.video.context') !== 'outstream') { + return false; + } + + // ad unit renderer could be on the adUnit.mediaTypes.video level or adUnit level + let renderer = deepAccess(bid, 'mediaTypes.video.renderer'); + if (!renderer) { + renderer = deepAccess(bid, 'renderer'); + } + + 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; +} + export const spec = { code: BIDDER_CODE, @@ -1198,6 +1609,7 @@ export const spec = { const paramsSize = deepAccess(bid, 'params.size'); const mediaTypeBannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); const mediaTypeVideoRef = deepAccess(bid, 'mediaTypes.video'); + const mediaTypeNativeRef = deepAccess(bid, 'mediaTypes.native'); const mediaTypeVideoPlayerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); const hasBidFloor = bid.params.hasOwnProperty('bidFloor'); const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); @@ -1245,7 +1657,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 }); @@ -1253,7 +1675,8 @@ export const spec = { return false; } } - return true; + + return nativeMediaTypeValid(mediaTypeNativeRef); }, /** @@ -1267,41 +1690,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; @@ -1322,13 +1736,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; }, @@ -1363,8 +1780,12 @@ export const spec = { const bidRequest = getBidRequest(innerBids[j].impid, requestBid.imp, bidderRequest.validBidRequests); bid = parseBid(innerBids[j], responseBody.cur, bidRequest); - if (!deepAccess(bid, 'mediaTypes.video.renderer') && deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { - bid.renderer = createRenderer(innerBids[j].bidId); + if (bid.mediaType === VIDEO && isIndexRendererPreferred(bidRequest)) { + const renderUrl = deepAccess(responseBody, 'ext.videoplayerurl'); + bid.renderer = createRenderer(innerBids[j].bidId, renderUrl); + if (!bid.renderer) { + continue; + } } bids.push(bid); @@ -1403,11 +1824,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 59b699bad2d..790e895bedf 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,27 +67,82 @@ 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 | --- | --- | --- | --- | 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 is provided. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` | size | Optional (Deprecated)| Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.video.playerSize`. Examples: `[300, 250]`, `[300, 600]` | video | Optional | Hash | The video object will serve as the properties of the video ad. You can create any field under the video object that is mentioned in the `OpenRTB Spec v2.5`. Some fields like `mimes, protocols, minduration, maxduration` are required. Properties not defined at this level, will be pulled from the Adunit level. -|video.w| Required | Integer | The video player size width in pixels that will be passed to demand partners. -|video.h| Required | Integer | The video player size height in pixels that will be passed to demand partners. -|video.playerSize| Optional* | Integer | The video player size that will be passed to demand partners. * In the absence of `video.w` and `video.h`, this field is required. -| video.mimes | Required | String[] | Array list of content MIME types supported. Popular MIME types include, but are not limited to, `"video/x-ms- wmv"` for Windows Media and `"video/x-flv"` for Flash Video. +|video.w| Required | Integer | The width of the video player in pixels that will be passed to demand partners.
*If you are using Index’s outstream player and have placed the `video` object at the `bidder` level, this is a required field. You must define the size of the video player using the `video.w` and `video.h` parameters, with a minimum video player size of 300 x 250. +|video.h| Required | Integer | The height of the video player in pixels that will be passed to demand partners.
*If you are using Index’s outstream player and have placed the `video` object at the `bidder` level, this is a required field. You must define the size of the video player using the `video.w` and `video.h` parameters, with a minimum video player size of 300 x 250. +|video.playerSize| Optional* | Array[Integer,Integer] | The video player size that will be passed to demand partners.
*If you are using Index’s outstream player and have placed the `video` object at the `adUnit` level, this is a required field. You must define the size of the video player using this parameter, with a minimum video player size of 300 x 250. +| video.mimes | Required | String[] | If you are using Index’s outstream video player and want to learn more about what is supported, see [List of supported OpenRTB bid request fields for Sellers](https://kb.indexexchange.com/publishers/openrtb_integration/list_of_supported_openrtb_bid_request_fields_for_sellers.htm#Video). |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 @@ -111,9 +177,7 @@ Both video and banner params will be read from the `adUnits[].mediaTypes.video` The examples in this guide assume the following starting configuration (you may remove banner or video, if either does not apply). -In regards to video, `context` can either be `'instream'` or `'outstream'`. Note that `outstream` requires additional configuration on the adUnit. - - +In regards to video, `context` can either be `'instream'` or `'outstream'`. ```javascript var adUnits = [{ @@ -195,9 +259,9 @@ var adUnits = [{ context: 'instream', playerSize: [300, 250], mimes: [ - 'video/mp4', - 'video/webm' - ], + 'video/mp4', + 'video/webm' + ], minduration: 0, maxduration: 60, protocols: [6] @@ -224,45 +288,53 @@ Please note that you can re-use the existing `siteId` within the same flex position. **Video (Outstream):** -Note that currently, outstream video rendering must be configured by the publisher. In the adUnit, a `renderer` object must be defined, which includes a `url` pointing to the video rendering script, and a `render` function for creating the video player. See http://prebid.org/dev-docs/show-outstream-video-ads.html for more information. + +Publishers have two options to receive outstream video demand from Index: +* Using Index’s outstream video player +* In an outstream video configuration set up by the publisher. For more information, see [Prebid’s documentation on how to show video ads.](https://docs.prebid.org/dev-docs/show-outstream-video-ads.html) + +**Index’s outstream video player** +Publishers who are using Index as a bidding adapter in Prebid.js can show outstream video ads on their site from us by using Index’s outstream video player. This allows a video ad to display inside of a video player and can be placed anywhere on a publisher’s site, such as in-article, in-feed, and more. + +Define a new `video` object for our outstream video player at either the adUnit level or the `bidder` level. If you are setting it at the bidder level, define the size of the video player using the parameters `video.h` and `video.w`. If you are setting it at the `adUnit` level, define the size using video.playerSize. + +For more information on how to structure the `video` object, refer to the following code example: + ```javascript var adUnits = [{ - code: 'video-div-a', + code: 'div-gpt-ad-1571167646410-1', mediaTypes: { video: { + playerSize: [640, 360], context: 'outstream', - playerSize: [300, 250], - mimes: [ - 'video/mp4', - 'video/webm' - ], - minduration: 0, - maxduration: 60, - protocols: [6] - } - }, - renderer: { - url: 'https://test.com/my-video-player.js', - render: function (bid) { - ... + api: [2], + protocols: [2, 3, 5, 6], + minduration: 5, + maxduration: 30, + mimes: ['video/mp4', 'application/javascript'], + placement: 5 } }, bids: [{ bidder: 'ix', params: { - siteId: '12345', + siteId: '715964' video: { - // If required, use this to override mediaTypes.video.XX properties + playerConfig: { + floatOnScroll: true, + floatSize: [300,250] + } } } }] }]; ``` +Please note that your use of the outstream video player will be governed by and subject to the terms and conditions of i) any master services or license agreement entered into by you and Index Exchange; ii) the information provided on our knowledge base linked [here](https://kb.indexexchange.com/publishers/prebid_integration/outstream_video_prebidjs.htm) and [here](https://kb.indexexchange.com/publishers/guidelines/standard_contractual_clauses.htm), and iii) our [Privacy Policy](https://www.indexexchange.com/privacy/). Your use of Index’s outstream video player constitutes your acknowledgement and acceptance of the foregoing. #### Video Caching -Note that the IX adapter expects a client-side Prebid Cache to be enabled for video bidding. +Note that the IX adapter expects a client-side Prebid Cache to be enabled for instream video bidding. ``` pbjs.setConfig({ @@ -293,21 +365,48 @@ pbjs.setConfig({ 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 - } - }); + ix: { + detectMissingSizes: false + } +}); ``` OR ``` pbjs.setBidderConfig({ - bidders: ["ix"], - config: { - ix: { - detectMissingSizes: false - } - } - }); + bidders: ["ix"], + config: { + ix: { + detectMissingSizes: false + } + } +}); +``` + +**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 +502,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 +519,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 de509853fed..f6f58883990 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -1,11 +1,12 @@ -import { logWarn, parseUrl, deepAccess, isArray } 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'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -27,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) {} } @@ -46,22 +47,29 @@ 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; } +// device in the payload had been a simple string ('desktop', 'mobile') +// Now changed to an object. yes the backend is able to handle it. function getDevice_() { - return ((/(ios|ipod|ipad|iphone|android|blackberry|iemobile|opera mini|webos)/i).test(navigator.userAgent) - ? 'mobile' : 'desktop'); + const device = config.getConfig('device') || {}; + device.w = device.w || window.innerWidth; + device.h = device.h || window.innerHeight; + device.ua = device.ua || navigator.userAgent; + device.dnt = getDNT() ? 1 : 0; + device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + return device; } function pingTracking_(endpointOverride, qpobj) { @@ -108,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) { @@ -121,6 +131,17 @@ function getMiscDims_() { return ret; } +/* function addUserId(eids, id, source, rti) { + if (id) { + if (rti) { + eids.push({ source, id, rti_partner: rti }); + } else { + eids.push({ source, id }); + } + } + return eids; +} */ + // easier for replacement in the unit test export const internal = { getDevice: getDevice_, @@ -163,7 +184,23 @@ export const spec = { } let ids = fetchIds_(); + let eids = []; let miscDims = internal.getMiscDims(); + let schain = deepAccess(validBidRequests[0], 'schain'); + + // all available user ids are sent to our backend in the standard array layout: + if (validBidRequests[0].userId) { + let eids1 = createEidsArray(validBidRequests[0].userId); + if (eids1.length) { + eids = eids1; + } + } + // we want to send this blob of info to our backend: + let pg = config.getConfig('priceGranularity'); + if (!pg) { + pg = {}; + } + let transformedParams = Object.assign({}, { auctionid: bidderRequest.auctionId, timeout: bidderRequest.timeout, @@ -174,6 +211,9 @@ export const spec = { pageurl: miscDims.pageurl, mkeywords: miscDims.mkeywords, bids: bids, + eids: eids, + schain: schain, + pricegranularity: pg, cfg: jixieCfgBlob }, ids); return Object.assign({}, { diff --git a/modules/jixieBidAdapter.md b/modules/jixieBidAdapter.md index d9c1f19541d..c0a1a965e87 100644 --- a/modules/jixieBidAdapter.md +++ b/modules/jixieBidAdapter.md @@ -7,6 +7,7 @@ Maintainer: contact@jixie.io # Description Module that connects to Jixie demand source to fetch bids. +All prebid-supported user ids are sent to Jixie endpoint, if available. # Test Parameters ``` diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js index 9993421ad1a..7f154614e4d 100644 --- a/modules/justpremiumBidAdapter.js +++ b/modules/justpremiumBidAdapter.js @@ -4,8 +4,7 @@ import { deepAccess } from '../src/utils.js'; const BIDDER_CODE = 'justpremium' const GVLID = 62 const ENDPOINT_URL = 'https://pre.ads.justpremium.com/v/2.0/t/xhr' -const JP_ADAPTER_VERSION = '1.8.2' -const pixels = [] +const JP_ADAPTER_VERSION = '1.8.3' export const spec = { code: BIDDER_CODE, @@ -26,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, @@ -114,8 +114,10 @@ export const spec = { return bidResponses }, - getUserSyncs: function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { let url = 'https://pre.ads.justpremium.com/v/1.0/t/sync' + '?_c=' + 'a' + Math.random().toString(36).substring(7) + Date.now(); + let pixels = [] + if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean') && gdprConsent.gdprApplies && gdprConsent.consentString) { url = url + '&consentString=' + encodeURIComponent(gdprConsent.consentString) } @@ -128,6 +130,10 @@ export const spec = { url: url }) } + if (syncOptions.pixelEnabled && serverResponses.length !== 0) { + const pxsFromResponse = serverResponses.map(res => res?.body?.pxs).reduce((acc, cur) => acc.concat(cur), []).filter((obj) => obj !== undefined); + pixels = [...pixels, ...pxsFromResponse]; + } return pixels }, } diff --git a/modules/justpremiumBidAdapter.md b/modules/justpremiumBidAdapter.md index 45dcb7b7f99..e107cb80958 100644 --- a/modules/justpremiumBidAdapter.md +++ b/modules/justpremiumBidAdapter.md @@ -2,7 +2,6 @@ **Module Name**: Justpremium Bidder Adapter **Module Type**: Bidder Adapter -**Maintainer**: headerbidding-dev@justpremium.com # Description diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 814aed59d85..342531ba26e 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -12,11 +12,12 @@ 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'; const SUBMODULE_NAME = 'jwplayer'; +const JWPLAYER_DOMAIN = SUBMODULE_NAME + '.com'; const segCache = {}; const pendingRequests = {}; let activeRequestCount = 0; @@ -69,7 +70,7 @@ export function fetchTargetingForMediaId(mediaId) { const ajax = ajaxBuilder(); // TODO: Avoid checking undefined vs null by setting a callback to pendingRequests. pendingRequests[mediaId] = null; - ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, { + ajax(`https://cdn.${JWPLAYER_DOMAIN}/v2/media/${mediaId}`, { success: function (response) { const segment = parseSegment(response); cacheSegments(segment, mediaId); @@ -129,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 { @@ -141,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) { @@ -155,10 +156,13 @@ export function enrichAdUnits(adUnits) { if (!vat) { return; } - const contentId = getContentId(vat.mediaID); - const contentData = getContentData(vat.segments); + const mediaId = vat.mediaID; + const contentId = getContentId(mediaId); + const contentSegments = getContentSegments(vat.segments); + const contentData = getContentData(mediaId, contentSegments); const targeting = formatTargetingResponse(vat); enrichBids(adUnit.bids, targeting, contentId, contentData); + addOrtbSiteContent(ortb2Fragments.global, contentId, contentData); }; loadVat(jwTargeting, onVatResponse); }); @@ -263,7 +267,7 @@ export function getContentId(mediaID) { return 'jw_' + mediaID; } -export function getContentData(segments) { +export function getContentSegments(segments) { if (!segments || !segments.length) { return; } @@ -276,21 +280,40 @@ export function getContentData(segments) { return convertedSegments; }, []); - return { - name: 'jwplayer', - ext: { - segtax: 502 - }, - segment: formattedSegments + return formattedSegments; +} + +export function getContentData(mediaId, segments) { + if (!mediaId && !segments) { + return; + } + + const contentData = { + name: JWPLAYER_DOMAIN, + ext: {} }; + + if (mediaId) { + contentData.ext.cids = [mediaId]; + } + + if (segments) { + contentData.segment = segments; + contentData.ext.segtax = 502; + } + + return contentData; } -export function addOrtbSiteContent(bid, contentId, contentData) { +export function addOrtbSiteContent(ortb2, contentId, contentData) { if (!contentId && !contentData) { return; } - let ortb2 = bid.ortb2 || {}; + if (ortb2 == null) { + ortb2 = {}; + } + let site = ortb2.site = ortb2.site || {}; let content = site.content = site.content || {}; @@ -298,12 +321,17 @@ export function addOrtbSiteContent(bid, contentId, contentData) { content.id = contentId; } + const currentData = content.data = content.data || []; + // remove old jwplayer data + const data = currentData.filter(datum => datum.name !== JWPLAYER_DOMAIN); + if (contentData) { - const data = content.data = content.data || []; data.push(contentData); } - bid.ortb2 = ortb2; + content.data = data; + + return ortb2; } function enrichBids(bids, targeting, contentId, contentData) { @@ -313,7 +341,6 @@ function enrichBids(bids, targeting, contentId, contentData) { bids.forEach(bid => { addTargetingToBid(bid, targeting); - addOrtbSiteContent(bid, contentId, contentData); }); } @@ -334,7 +361,7 @@ export function addTargetingToBid(bid, targeting) { function getPlayer(playerID) { const jwplayer = window.jwplayer; if (!jwplayer) { - logError('jwplayer.js was not found on page'); + logError(SUBMODULE_NAME + '.js was not found on page'); return; } diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 77f65909040..479829196ed 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -95,9 +95,10 @@ Example: content: { id: 'jw_abc123', data: [{ - name: 'jwplayer', + name: 'jwplayer.com', ext: { - segtax: 502 + segtax: 502, + cids: ['abc123'] }, segment: [{ id: '123' @@ -117,8 +118,9 @@ where: - `content` is an object containing metadata for the media. It may contain the following information: - `id` is a unique identifier for the specific media asset - `data` is an array containing segment taxonomy objects that have the following parameters: - - `name` is the `jwplayer` string indicating the provider name + - `name` is the `jwplayer.com` string indicating the provider name - `ext.segtax` whose `502` value is the unique identifier for JW Player's proprietary taxonomy + - `ext.cids` is an array containing the list of extended content ids as defined in [oRTB's community extensions](https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/extended-content-ids.md#example---content-id-and-seller-defined-context). - `segment` is an array containing the segment taxonomy values as an object where: - `id` is the string representation of the data segment value. diff --git a/modules/kargoAnalyticsAdapter.js b/modules/kargoAnalyticsAdapter.js index 83c20846c0d..8ddf14aaa83 100644 --- a/modules/kargoAnalyticsAdapter.js +++ b/modules/kargoAnalyticsAdapter.js @@ -1,14 +1,96 @@ +import { logError } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; import adapter from '../src/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 098e38b2c43..3027a8e75b8 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -1,4 +1,4 @@ -import { _each } from '../src/utils.js'; +import { _each, buildUrl, triggerPixel } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -55,6 +55,10 @@ export const spec = { }, bidIDs, bidSizes, + device: { + width: window.screen.width, + height: window.screen.height + }, prebidRawBidRequests: validBidRequests }, spec._getAllMetadata(tdid, bidderRequest.uspConsent, bidderRequest.gdprConsent)); const encodedParams = encodeURIComponent(JSON.stringify(transformedParams)); @@ -70,27 +74,41 @@ export const spec = { const bidResponses = []; for (let bidId in bids) { let adUnit = bids[bidId]; - let meta; + let meta = { + mediaType: BANNER + }; + if (adUnit.metadata && adUnit.metadata.landingPageDomain) { - meta = { - clickUrl: adUnit.metadata.landingPageDomain[0], - advertiserDomains: adUnit.metadata.landingPageDomain - }; + meta.clickUrl = adUnit.metadata.landingPageDomain[0]; + meta.advertiserDomains = adUnit.metadata.landingPageDomain; + } + + if (adUnit.mediaType && SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType)) { + meta.mediaType = adUnit.mediaType; } - bidResponses.push({ + + const bidResponse = { + ad: adUnit.adm, requestId: bidId, cpm: Number(adUnit.cpm), width: adUnit.width, height: adUnit.height, - ad: adUnit.adm, ttl: 300, creativeId: adUnit.id, dealId: adUnit.targetingCustom, netRevenue: true, currency: adUnit.currency || bidRequest.currency, + mediaType: meta.mediaType, meta: meta - }); + }; + + if (meta.mediaType == VIDEO) { + bidResponse.vastXml = adUnit.adm; + } + + bidResponses.push(bidResponse); } + return bidResponses; }, getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { @@ -118,6 +136,15 @@ export const spec = { return syncs; }, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + onTimeout: function(timeoutData) { + if (timeoutData == null) { + return; + } + + timeoutData.forEach((bid) => { + this._sendTimeoutData(bid.auctionId, bid.timeout); + }); + }, // PRIVATE _readCookie(name) { @@ -213,6 +240,7 @@ export const spec = { _getAllMetadata(tdid, usp, gdpr) { return { userIDs: spec._getUserIds(tdid, usp, gdpr), + // TODO: this should probably look at refererInfo pageURL: window.location.href, rawCRB: spec._readCookie('krg_crb'), rawCRBLocalStorage: spec._getLocalStorageSafely('krg_crb') @@ -248,6 +276,24 @@ export const spec = { } catch (e) { return ''; } + }, + + _sendTimeoutData(auctionId, auctionTimeout) { + let params = { + aid: auctionId, + ato: auctionTimeout, + }; + + try { + let timeoutRequestUrl = buildUrl({ + protocol: 'https', + hostname: 'krk.kargo.com', + pathname: '/api/v1/event/timeout', + search: params + }); + + triggerPixel(timeoutRequestUrl); + } catch (e) {} } }; registerBidder(spec); diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 80aa038a9f7..1dc22d0099a 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -1,4 +1,12 @@ -import { deepAccess, isStr, replaceAuctionPrice, triggerPixel, isArray, parseQueryStringParameters, getWindowSelf } from '../src/utils.js'; +import { + deepAccess, + getWindowSelf, + isArray, + isStr, + parseQueryStringParameters, + replaceAuctionPrice, + triggerPixel +} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -6,19 +14,26 @@ import {getRefererInfo} from '../src/refererDetection.js'; const BIDDER_CODE = 'kobler'; const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call'; +const DEV_BIDDER_ENDPOINT = 'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'; const TIMEOUT_NOTIFICATION_ENDPOINT = 'https://bid.essrtb.com/notify/prebid_timeout'; const SUPPORTED_CURRENCY = 'USD'; const DEFAULT_TIMEOUT = 1000; const TIME_TO_LIVE_IN_SECONDS = 10 * 60; export const isBidRequestValid = function (bid) { - return !!(bid && bid.bidId && bid.params && bid.params.placementId); + if (!bid || !bid.bidId) { + return false; + } + + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes', bid.sizes); + return isArray(sizes) && sizes.length > 0; }; export const buildRequests = function (validBidRequests, bidderRequest) { + const bidderEndpoint = isTest(validBidRequests[0]) ? DEV_BIDDER_ENDPOINT : BIDDER_ENDPOINT; return { method: 'POST', - url: BIDDER_ENDPOINT, + url: bidderEndpoint, data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), options: { contentType: 'application/json' @@ -27,14 +42,11 @@ export const buildRequests = function (validBidRequests, bidderRequest) { }; export const interpretResponse = function (serverResponse) { - const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; const res = serverResponse.body; const bids = [] if (res) { res.seatbid.forEach(sb => { sb.bid.forEach(b => { - const adWithCorrectCurrency = b.adm - .replace(/\${AUCTION_PRICE_CURRENCY}/g, adServerPriceCurrency); bids.push({ requestId: b.impid, cpm: b.price, @@ -45,7 +57,7 @@ export const interpretResponse = function (serverResponse) { dealId: b.dealid, netRevenue: true, ttl: TIME_TO_LIVE_IN_SECONDS, - ad: adWithCorrectCurrency, + ad: b.adm, nurl: b.nurl, meta: { advertiserDomains: b.adomain @@ -58,13 +70,15 @@ export const interpretResponse = function (serverResponse) { }; export const onBidWon = function (bid) { - const cpm = bid.cpm || 0; - const cpmCurrency = bid.currency || SUPPORTED_CURRENCY; + // We intentionally use the price set by the publisher to replace the ${AUCTION_PRICE} macro + // instead of the `originalCpm` here. This notification is not used for billing, only for extra logging. + const publisherPrice = bid.cpm || 0; + const publisherCurrency = bid.currency || config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; const adServerPrice = deepAccess(bid, 'adserverTargeting.hb_pb', 0); const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; if (isStr(bid.nurl) && bid.nurl !== '') { - const winNotificationUrl = replaceAuctionPrice(bid.nurl, bid.originalCpm || cpm) - .replace(/\${AUCTION_PRICE_CURRENCY}/g, cpmCurrency) + const winNotificationUrl = replaceAuctionPrice(bid.nurl, publisherPrice) + .replace(/\${AUCTION_PRICE_CURRENCY}/g, publisherCurrency) .replace(/\${AD_SERVER_PRICE}/g, adServerPrice) .replace(/\${AD_SERVER_PRICE_CURRENCY}/g, adServerPriceCurrency); triggerPixel(winNotificationUrl); @@ -73,17 +87,13 @@ export const onBidWon = function (bid) { export const onTimeout = function (timeoutDataArray) { if (isArray(timeoutDataArray)) { - const refererInfo = getRefererInfo(); - const pageUrl = (refererInfo && refererInfo.referer) - ? refererInfo.referer - : window.location.href; + const pageUrl = getPageUrlFromRefererInfo(); timeoutDataArray.forEach(timeoutData => { const query = parseQueryStringParameters({ ad_unit_code: timeoutData.adUnitCode, auction_id: timeoutData.auctionId, bid_id: timeoutData.bidId, timeout: timeoutData.timeout, - placement_id: deepAccess(timeoutData, 'params.0.placementId'), page_url: pageUrl, }); const timeoutNotificationUrl = `${TIMEOUT_NOTIFICATION_ENDPOINT}?${query}`; @@ -92,13 +102,30 @@ export const onTimeout = function (timeoutDataArray) { } }; +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.page) + ? bidderRequest.refererInfo.page + : window.location.href; +} + +function getPageUrlFromRefererInfo() { + const refererInfo = getRefererInfo(); + return (refererInfo && refererInfo.page) + ? refererInfo.page + : window.location.href; +} + function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { const imps = validBidRequests.map(buildOpenRtbImpObject); const timeout = bidderRequest.timeout || config.getConfig('bidderTimeout') || DEFAULT_TIMEOUT; - const pageUrl = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) - ? bidderRequest.refererInfo.referer - : window.location.href; - + const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest) const request = { id: bidderRequest.auctionId, at: 1, @@ -106,13 +133,12 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { cur: [SUPPORTED_CURRENCY], imp: imps, device: { - devicetype: getDevice(), - geo: getGeo(validBidRequests[0]) + devicetype: getDevice() }, site: { page: pageUrl, }, - test: getTest(validBidRequests[0]) + test: getTestAsNumber(validBidRequests[0]) }; return JSON.stringify(request); @@ -128,14 +154,8 @@ function buildOpenRtbImpObject(validBidRequest) { banner: { format: buildFormatArray(sizes), w: mainSize[0], - h: mainSize[1], - ext: { - kobler: { - pos: getPosition(validBidRequest) - } - } + h: mainSize[1] }, - tagid: validBidRequest.params.placementId, bidfloor: floorInfo.floor, bidfloorcur: floorInfo.currency, pmp: buildPmpObject(validBidRequest) @@ -157,17 +177,12 @@ function getDevice() { return 2; // personal computers } -function getGeo(validBidRequest) { - if (validBidRequest.params.zip) { - return { - zip: validBidRequest.params.zip - }; - } - return {}; +function getTestAsNumber(validBidRequest) { + return isTest(validBidRequest) ? 1 : 0; } -function getTest(validBidRequest) { - return validBidRequest.params.test ? 1 : 0; +function isTest(validBidRequest) { + return validBidRequest.params && validBidRequest.params.test === true; } function getSizes(validBidRequest) { @@ -188,10 +203,6 @@ function buildFormatArray(sizes) { }); } -function getPosition(validBidRequest) { - return parseInt(validBidRequest.params.position) || 0; -} - function getFloorInfo(validBidRequest, mainSize) { if (typeof validBidRequest.getFloor === 'function') { const sizeParam = mainSize[0] === 0 && mainSize[1] === 0 ? '*' : mainSize; @@ -209,11 +220,11 @@ function getFloorInfo(validBidRequest, mainSize) { } function getFloorPrice(validBidRequest) { - return parseFloat(validBidRequest.params.floorPrice) || 0.0; + return parseFloat(deepAccess(validBidRequest, 'params.floorPrice', 0.0)); } function buildPmpObject(validBidRequest) { - if (validBidRequest.params.dealIds) { + if (validBidRequest.params && validBidRequest.params.dealIds && isArray(validBidRequest.params.dealIds)) { return { deals: validBidRequest.params.dealIds.map(dealId => { return { diff --git a/modules/koblerBidAdapter.md b/modules/koblerBidAdapter.md index 7a7b2388367..63b0c3ead68 100644 --- a/modules/koblerBidAdapter.md +++ b/modules/koblerBidAdapter.md @@ -12,14 +12,15 @@ This adapter currently only supports Banner Ads. # Parameters -| Parameter (in `params`) | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| placementId | Required | String | The identifier of the placement, it has to be issued by Kobler. | `'xjer0ch8'` | -| zip | Optional | String | Zip code of the user or the medium. When multiple ad units are submitted together, it is enough to set this parameter on the first one. | `'102 22'` | -| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Defaults to false. | `true` | -| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` | -| position | Optional | Integer | The position of the ad unit. Can be used to differentiate between ad units if the same placement ID is used across multiple ad units. The first ad unit should have a `position` of 0, the second one should have a `position` of 1 and so on. Defaults to 0. | `1` | -| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` | +| Parameter (in `params`) | Scope | Type | Description | Example | +|-------------------------|----------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------| +| test | Optional | Boolean | Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one. Enables providing a custom URL through config.pageUrl. Defaults to false. | `true` | +| floorPrice | Optional | Float | Floor price in CPM and USD. Can be used as an alternative to the [Floors module](https://docs.prebid.org/dev-docs/modules/floors.html), which is also supported by this adapter. Defaults to 0. | `5.0` | +| dealIds | Optional | Array of Strings | Array of deal IDs. | `['abc328745', 'mxw243253']` | + +## Implicit parameters + +Kobler identifies the placement using the combination of the page URL and the allowed sizes. As a result, it's important that the correct sizes are provided in `banner.sizes` in order for Kobler to correctly identify the placement. The main, desired format should be the first element of this array. # Test Parameters ```javascript @@ -31,17 +32,14 @@ This adapter currently only supports Banner Ads. } }, bids: [{ - bidder: 'kobler', - params: { - placementId: 'k5H7et3R0' - } + bidder: 'kobler' }] }]; ``` In order to see a sample bid from Kobler (without a proper setup), you have to also do the following: -- Change the [`refererInfo` function](https://github.com/prebid/Prebid.js/blob/master/src/refererDetection.js) to return `'https://www.tv2.no/a/11734615'` as a [`referer`](https://github.com/prebid/Prebid.js/blob/caead3ccccc448e4cd09d074fd9f8833f56fe9b3/src/refererDetection.js#L169). This is necessary because Kobler only bids on recognized articles. -- Change the adapter's [`BIDDER_ENDPOINT`](https://github.com/prebid/Prebid.js/blob/master/modules/koblerBidAdapter.js#L8) to `'https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'`. This endpoint belongs to the development server that is set up to always return a bid for the correct `placementId` and page URL combination. +- Set the `test` parameter to `true`. +- Set `config.pageUrl` to `'https://www.tv2.no/mening-og-analyse/14555348/'`. This is necessary because Kobler only bids on recognized articles. Kobler runs its own test campaign to make sure there is always a bid for this specific page URL. # Test Optional Parameters ```javascript @@ -55,11 +53,8 @@ In order to see a sample bid from Kobler (without a proper setup), you have to a bids: [{ bidder: 'kobler', params: { - placementId: 'k5H7et3R0', - zip: '102 22', test: true, floorPrice: 5.0, - position: 1, dealIds: ['abc328745', 'mxw243253'] } }] 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/krushmediaBidAdapter.js b/modules/krushmediaBidAdapter.js index da68bddcb7b..6dce40524a7 100644 --- a/modules/krushmediaBidAdapter.js +++ b/modules/krushmediaBidAdapter.js @@ -51,8 +51,9 @@ export const spec = { buildRequests: (validBidRequests = [], bidderRequest) => { 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/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..f1156376ff5 --- /dev/null +++ b/modules/kueezBidAdapter.js @@ -0,0 +1,470 @@ +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), + 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..b2f3ccf9a7c --- /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: 1, + coppa: config.getConfig('coppa') == true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined + } + + return { + method: 'GET', + url: getBidRequestUrl(aimXR), + data: payload, + options: { + withCredentials: false + }, + }; + }); + }, + + interpretResponse: function(serverResponse) { + const response = serverResponse && serverResponse.body; + const bidResponses = []; + + if (!response) { + 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.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/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 32e09e4b28e..104ff667fcb 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -59,7 +59,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)); @@ -67,7 +67,7 @@ export const spec = { var adRequests = bidRequests.map(bidToAdRequest); if (eids) { - ortb2 = mergeDeep(ortb2 || {}, eids); + ortb2 = mergeDeep(mergeDeep({}, ortb2 || {}), eids); } const payload = { @@ -276,8 +276,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/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index 275ab38915d..6d2766ff2f1 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -36,9 +36,9 @@ export const spec = { const serverRequestObjects = []; const UTC_OFFSET = new Date().getTimezoneOffset(); const UA = navigator.userAgent; - const IP = navigator.ip ? navigator.ip : 'prebid.js'; 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; @@ -60,8 +60,7 @@ export const spec = { ua: UA, geo: { utcoffset: UTC_OFFSET - }, - ip: IP + } }, user: { ext: {} 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/logicadBidAdapter.js b/modules/logicadBidAdapter.js index 2c919f9c157..4f996ba3f09 100644 --- a/modules/logicadBidAdapter.js +++ b/modules/logicadBidAdapter.js @@ -60,7 +60,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 e1319d08766..a05434e8ee5 100644 --- a/modules/loglyliftBidAdapter.js +++ b/modules/loglyliftBidAdapter.js @@ -1,13 +1,13 @@ import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { NATIVE } from '../src/mediaTypes.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; const BIDDER_CODE = 'loglylift'; const ENDPOINT_URL = 'https://bid.logly.co.jp/prebid/client/v1'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [NATIVE], + supportedMediaTypes: [BANNER, NATIVE], isBidRequestValid: function (bid) { return !!(bid.params && bid.params.adspotId); @@ -43,7 +43,8 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; - if (syncOptions.iframeEnabled && serverResponses.length > 0) { + // sync if mediaType is native because not native ad itself has a function for sync + if (syncOptions.iframeEnabled && serverResponses.length > 0 && serverResponses[0].body.bids[0].native) { syncs.push({ type: 'iframe', url: 'https://sync.logly.co.jp/sync/sync.html' @@ -68,8 +69,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/loglyliftBidAdapter.md b/modules/loglyliftBidAdapter.md index 9bca238b03e..5505d66957d 100644 --- a/modules/loglyliftBidAdapter.md +++ b/modules/loglyliftBidAdapter.md @@ -12,6 +12,22 @@ Currently module supports only native mediaType. # Test Parameters ``` var adUnits = [ + // Banner adUnit + { + code: 'test-banner-code', + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'loglylift', + params: { + adspotId: 1302078 + } + }] + }, // Native adUnit { code: 'test-native-code', 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..286e87668fa 100644 --- a/modules/lunamediahbBidAdapter.js +++ b/modules/lunamediahbBidAdapter.js @@ -35,8 +35,9 @@ export const spec = { buildRequests: (validBidRequests = [], bidderRequest) => { 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..31e2120d364 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -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; } diff --git a/modules/malltvAnalyticsAdapter.js b/modules/malltvAnalyticsAdapter.js index 3431681ef2f..a0e2a208bc9 100644 --- a/modules/malltvAnalyticsAdapter.js +++ b/modules/malltvAnalyticsAdapter.js @@ -184,5 +184,5 @@ malltvAnalyticsAdapter.enableAnalytics = function (config) { adapterManager.registerAnalyticsAdapter({ adapter: malltvAnalyticsAdapter, - code: 'malltvAnalytics' + code: 'malltv' }) diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js index 53f745d4004..6466aa4feed 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; } diff --git a/modules/mantisBidAdapter.js b/modules/mantisBidAdapter.js index 8d62b0ffba7..4520bad0f3a 100644 --- a/modules/mantisBidAdapter.js +++ b/modules/mantisBidAdapter.js @@ -1,5 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; export const storage = getStorageManager({bidderCode: 'mantis'}); @@ -10,12 +11,7 @@ function inIframe() { return true; } } -function pixel(url, parent) { - var img = document.createElement('img'); - img.src = url; - img.style.cssText = 'display:none !important;'; - (parent || document.body).appendChild(img); -} + export function onVisible(win, element, doOnVisible, time, pct) { var started = null; var notified = false; @@ -301,9 +297,9 @@ export function iframePostMessage (win, name, callback) { onMessage('iframe', function (data) { if (window.$sf) { - sfPostMessage(window.$sf, data.width, data.height, () => pixel(data.pixel)); + sfPostMessage(window.$sf, data.width, data.height, () => ajax(data.pixel)); } else { - iframePostMessage(window, data.frame, () => pixel(data.pixel)); + iframePostMessage(window, data.frame, () => ajax(data.pixel)); } }); 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..f80e6e1f749 100644 --- a/modules/mathildeadsBidAdapter.js +++ b/modules/mathildeadsBidAdapter.js @@ -125,7 +125,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 +133,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..8f15af72235 100644 --- a/modules/mediaforceBidAdapter.js +++ b/modules/mediaforceBidAdapter.js @@ -113,7 +113,8 @@ export const spec = { 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 +157,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..4cd6b60e8a2 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -8,6 +8,7 @@ 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'; const BIDDER_CODE = 'mediafuse'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -228,7 +229,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(',') @@ -249,7 +251,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); @@ -479,16 +480,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,7 +488,7 @@ function formatRequest(payload, bidderRequest) { let endpointUrl = URL; - if (!hasPurpose1Consent(bidderRequest)) { + if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { endpointUrl = URL_SIMPLE; } diff --git a/modules/mediagoBidAdapter.md b/modules/mediagoBidAdapter.md deleted file mode 100644 index 87c38f662a3..00000000000 --- a/modules/mediagoBidAdapter.md +++ /dev/null @@ -1,33 +0,0 @@ -# Overview - -``` -Module Name: MediaGo Bidder Adapter -Module Type: Bidder Adapter -Maintainer: fangsimin@baidu.com -``` - -# Description - -Module that connects to MediaGo's demand sources - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - bids: [ - { - bidder: "mediago", - params: { - token: '' // required, send email to ext_mediago_am@baidu.com to get the corresponding token - } - } - ] - } - ]; -``` diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index 5eb32a3f6e4..6a4092cef4b 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -5,7 +5,6 @@ import { deepClone, deepSetValue, getDNT, - getWindowTop, inIframe, isArray, isEmpty, @@ -16,7 +15,6 @@ import { logError, logWarn, mergeDeep, - parseUrl, triggerPixel } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -85,19 +83,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. @@ -650,21 +635,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 }); } diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 09ebbc9bc31..05c18a47f94 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.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 db3921c9a47..f17e136b2d2 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -1,9 +1,21 @@ -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'; const BIDDER_CODE = 'medianet'; const BID_URL = 'https://prebid.media.net/rtb/prebid'; @@ -19,6 +31,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,10 +41,10 @@ window.mnet = window.mnet || {}; window.mnet.queue = window.mnet.queue || []; mnData.urlData = { - domain: parseUrl(refererInfo.referer).hostname, - page: refererInfo.referer, + domain: refererInfo.domain, + page: refererInfo.page, isTop: refererInfo.reachedTop -} +}; $$PREBID_GLOBAL$$.medianetGlobals = $$PREBID_GLOBAL$$.medianetGlobals || {}; @@ -42,13 +56,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 +189,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 +323,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') } } diff --git a/modules/medianetRtdProvider.js b/modules/medianetRtdProvider.js index 07b1d66fbc5..3e01d0e5631 100644 --- a/modules/medianetRtdProvider.js +++ b/modules/medianetRtdProvider.js @@ -1,4 +1,5 @@ -import {insertElement, isEmptyStr, isFn, isStr, logError, mergeDeep} from '../src/utils.js'; +import {isEmptyStr, isFn, isStr, logError, mergeDeep} from '../src/utils.js'; +import {loadExternalScript} from '../src/adloader.js'; import {submodule} from '../src/hook.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {includes} from '../src/polyfill.js'; @@ -82,11 +83,8 @@ function executeCommand(command) { } function loadRtdScript(customerId) { - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.async = true; - script.src = getClientUrl(customerId, window.location.hostname); - insertElement(script, window.document, 'head'); + const url = getClientUrl(customerId, window.location.hostname); + loadExternalScript(url, MODULE_NAME) } function getAdUnits(adUnits, adUnitCodes) { diff --git a/modules/mediasniperBidAdapter.js b/modules/mediasniperBidAdapter.js new file mode 100644 index 00000000000..417642b7a6f --- /dev/null +++ b/modules/mediasniperBidAdapter.js @@ -0,0 +1,319 @@ +import { + deepAccess, + deepClone, + deepSetValue, + getBidIdParameter, + inIframe, + isArray, + isEmpty, + isFn, + isNumber, + isStr, + logError, + logMessage, + logWarn, + triggerPixel, +} from '../src/utils.js'; + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'mediasniper'; +const DEFAULT_BID_TTL = 360; +const DEFAULT_CURRENCY = 'RUB'; +const DEFAULT_NET_REVENUE = true; +const ENDPOINT = 'https://sapi.bumlam.com/prebid/'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + logMessage('Hello!! bid: ', JSON.stringify(bid)); + + if (!bid || isEmpty(bid)) { + return false; + } + + if (!bid.params || isEmpty(bid.params)) { + return false; + } + + if (!isStr(bid.params.placementId) && !isNumber(bid.params.placementId)) { + return false; + } + + const banner = deepAccess(bid, 'mediaTypes.banner', {}); + if (!banner || isEmpty(banner)) { + return false; + } + + const sizes = deepAccess(bid, 'mediaTypes.banner.sizes', []); + if (!isArray(sizes) || isEmpty(sizes)) { + return false; + } + + return true; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const payload = createOrtbTemplate(); + + deepSetValue(payload, 'id', bidderRequest.auctionId); + + validBidRequests.forEach((validBid) => { + let bid = deepClone(validBid); + + const imp = createImp(bid); + payload.imp.push(imp); + }); + + // params + const siteId = getBidIdParameter('siteid', validBidRequests[0].params) + ''; + deepSetValue(payload, 'site.id', siteId); + + // 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.page; + deepSetValue(payload, 'site.page', sitePage); + deepSetValue( + payload, + 'site.domain', + bidderRequest.refererInfo.domain + ); + + if (bidderRequest.refererInfo?.ref) { + deepSetValue(payload, 'site.ref', bidderRequest.refererInfo.ref); + } + } + } + + const request = { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(payload), + }; + + return request; + }, + + interpretResponse(serverResponse, bidRequest) { + const bidResponses = []; + + try { + if ( + serverResponse.body && + serverResponse.body.seatbid && + isArray(serverResponse.body.seatbid) + ) { + serverResponse.body.seatbid.forEach((bidderSeat) => { + if (!isArray(bidderSeat.bid) || !bidderSeat.bid.length) { + return; + } + + bidderSeat.bid.forEach((bid) => { + const newBid = { + requestId: bid.impid, + bidderCode: spec.code, + cpm: bid.price || 0, + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.adid || bid.id, + dealId: bid.dealid || null, + currency: serverResponse.body.cur || DEFAULT_CURRENCY, + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL, // seconds. https://docs.prebid.org/dev-docs/faq.html#does-prebidjs-cache-bids + ad: bid.adm, + mediaType: BANNER, + burl: bid.nurl, + meta: { + advertiserDomains: + Array.isArray(bid.adomain) && bid.adomain.length + ? bid.adomain + : [], + mediaType: BANNER, + }, + }; + + logMessage('answer: ', JSON.stringify(newBid)); + + bidResponses.push(newBid); + }); + }); + } + } catch (e) { + logError(BIDDER_CODE, e); + } + + return bidResponses; + }, + + onBidWon: function (bid) { + if (!bid.burl) { + return; + } + + const url = bid.burl.replace(/\$\{AUCTION_PRICE\}/, bid.cpm); + + triggerPixel(url); + }, +}; +registerBidder(spec); + +/** + * Returns an openRTB 2.5 object. + * This one will be populated at each step of the buildRequest process. + * + * @returns {object} + */ +function createOrtbTemplate() { + return { + id: '', + cur: [DEFAULT_CURRENCY], + imp: [], + site: {}, + device: { + ip: '', + js: 1, + ua: navigator.userAgent, + }, + user: {}, + }; +} + +/** + * Create the OpenRTB 2.5 imp object. + * + * @param {*} bid Prebid bid object from request + * @returns + */ +function createImp(bid) { + let placementId = ''; + if (isStr(bid.params.placementId)) { + placementId = bid.params.placementId; + } else if (isNumber(bid.params.placementId)) { + placementId = bid.params.placementId.toString(); + } + + const imp = { + id: bid.bidId, + tagid: placementId, + bidfloorcur: DEFAULT_CURRENCY, + secure: 1, + }; + + // There is no default floor. bidfloor is set only + // if the priceFloors module is activated and returns a valid floor. + const floor = getMinFloor(bid); + if (isNumber(floor)) { + imp.bidfloor = floor; + } + + // Only supports proper mediaTypes definition… + for (let mediaType in bid.mediaTypes) { + switch (mediaType) { + case BANNER: + imp.banner = createBannerImp(bid); + break; + } + } + + // dealid + const dealId = getBidIdParameter('dealid', bid.params); + if (dealId) { + imp.pmp = { + private_auction: 1, + deals: [ + { + id: dealId, + bidfloor: floor || 0, + bidfloorcur: DEFAULT_CURRENCY, + }, + ], + }; + } + + return imp; +} + +/** + * Returns floor from priceFloors module or MediaKey default value. + * + * @param {*} bid a Prebid.js bid (request) object + * @param {string} mediaType the mediaType or the wildcard '*' + * @param {string|array} size the size array or the wildcard '*' + * @returns {number|boolean} + */ +function getFloor(bid, mediaType, size = '*') { + if (!isFn(bid.getFloor)) { + return false; + } + + if (spec.supportedMediaTypes.indexOf(mediaType) === -1) { + logWarn( + `${BIDDER_CODE}: Unable to detect floor price for unsupported mediaType ${mediaType}. No floor will be used.` + ); + return false; + } + + const floor = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType, + size, + }); + + return !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY + ? floor.floor + : false; +} + +function getMinFloor(bid) { + const floors = []; + + for (let mediaType in bid.mediaTypes) { + const floor = getFloor(bid, mediaType); + + if (isNumber(floor)) { + floors.push(floor); + } + } + + if (!floors.length) { + return false; + } + + return floors.reduce((a, b) => { + return Math.min(a, b); + }); +} + +/** + * Returns an openRtb 2.5 banner object. + * + * @param {object} bid Prebid bid object from request + * @returns {object} + */ +function createBannerImp(bid) { + let sizes = bid.mediaTypes.banner.sizes; + const params = deepAccess(bid, 'params', {}); + + const banner = {}; + + banner.w = parseInt(sizes[0][0], 10); + banner.h = parseInt(sizes[0][1], 10); + + const format = []; + sizes.forEach(function (size) { + if (size.length && size.length > 1) { + format.push({ w: size[0], h: size[1] }); + } + }); + banner.format = format; + + banner.topframe = inIframe() ? 0 : 1; + banner.pos = params.pos || 0; + + return banner; +} diff --git a/modules/mediasniperBidAdapter.md b/modules/mediasniperBidAdapter.md new file mode 100644 index 00000000000..e47513c7fb2 --- /dev/null +++ b/modules/mediasniperBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: Mediasniper Bid Adapter +Module Type: Bidder Adapter +Maintainer: oleg@rtbtech.org +``` + +# Description + +Connects to Mediasniper demand source to fetch bids. + +# Test Parameters + +``` +var adUnits = [ +{ + code: 'test', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [{ + bidder: 'mediasniper', + params: { + placementId: "123456" + } + }] +}, +``` diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 427a16f1341..1be58501828 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -55,7 +55,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..19602c27093 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,7 +154,7 @@ 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') { @@ -162,15 +170,18 @@ 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) { @@ -181,6 +192,7 @@ export const merkleIdSubmodule = { return {callback: resp}; } } + logInfo('User ID - merkleId not refreshed'); return {id: storedId}; } diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 51b713c8958..7565b713e88 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -122,7 +122,8 @@ export const spec = { 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'); diff --git a/modules/microadBidAdapter.js b/modules/microadBidAdapter.js index 982bd61840a..77710584f41 100644 --- a/modules/microadBidAdapter.js +++ b/modules/microadBidAdapter.js @@ -51,8 +51,9 @@ export const spec = { 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), diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index 1a9ccfdf824..d953558bf31 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -1,17 +1,17 @@ -import { logWarn, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter } from '../src/utils.js'; +import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -const SUPPORTED_AD_TYPES = [VIDEO]; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'minutemedia'; -const ADAPTER_VERSION = '5.0.1'; +const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const CURRENCY = 'USD'; const SELLER_ENDPOINT = 'https://hb.minutemedia-prebid.com/'; const MODES = { - PRODUCTION: 'hb-mm', - TEST: 'hb-mm-test' + PRODUCTION: 'hb-mm-multi', + TEST: 'hb-multi-mm-test' } const SUPPORTED_SYNC_METHODS = { IFRAME: 'iframe', @@ -23,7 +23,7 @@ export const spec = { gvlid: 918, version: ADAPTER_VERSION, supportedMediaTypes: SUPPORTED_AD_TYPES, - isBidRequestValid: function(bidRequest) { + isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to MinuteMedia adapter'); return false; @@ -36,54 +36,70 @@ export const spec = { return true; }, - buildRequests: function (bidRequests, bidderRequest) { - if (bidRequests.length === 0) { - return []; - } + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; - const requests = []; + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; - bidRequests.forEach(bid => { - requests.push(buildVideoRequest(bid, bidderRequest)); - }); + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - return requests; + return { + method: 'POST', + url: getEndpoint(testMode), + data: combinedRequestsObject + } }, - interpretResponse: function({body}) { + interpretResponse: function ({body}) { const bidResponses = []; - const bidResponse = { - requestId: body.requestId, - cpm: body.cpm, - width: body.width, - height: body.height, - creativeId: body.requestId, - currency: body.currency, - netRevenue: body.netRevenue, - ttl: body.ttl || TTL, - vastXml: body.vastXml, - mediaType: VIDEO - }; - - if (body.adomain && body.adomain.length) { - bidResponse.meta = {}; - bidResponse.meta.advertiserDomains = body.adomain + 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); + }); } - bidResponses.push(bidResponse); return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.userSyncURL) { + if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { syncs.push({ type: 'iframe', - url: response.body.userSyncURL + url: response.body.params.userSyncURL }); } - if (syncOptions.pixelEnabled && isArray(response.body.userSyncPixels)) { - const pixels = response.body.userSyncPixels.map(pixel => { + if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel @@ -93,6 +109,16 @@ export const spec = { } } return syncs; + }, + onBidWon: function (bid) { + if (bid == null) { + return; + } + + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } } }; @@ -103,46 +129,33 @@ registerBidder(spec); * @param bid {bid} * @returns {Number} */ -function getFloor(bid) { +function getFloor(bid, mediaType) { if (!isFn(bid.getFloor)) { return 0; } let floorResult = bid.getFloor({ currency: CURRENCY, - mediaType: VIDEO, + mediaType: mediaType, size: '*' }); return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; } /** - * Build the video request - * @param bid {bid} - * @param bidderRequest {bidderRequest} - * @returns {Object} - */ -function buildVideoRequest(bid, bidderRequest) { - const sellerParams = generateParameters(bid, bidderRequest); - const {params} = bid; - return { - method: 'GET', - url: getEndpoint(params.testMode), - data: sellerParams - }; -} - -/** - * Get the the ad size from the bid + * Get the the ad sizes array from the bid * @param bid {bid} * @returns {Array} */ -function getSizes(bid) { - if (deepAccess(bid, 'mediaTypes.video.sizes')) { - return bid.mediaTypes.video.sizes[0]; +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) { - return bid.sizes[0]; + sizesArray = bid.sizes; } - return []; + + return sizesArray; } /** @@ -239,122 +252,181 @@ function getDeviceType(ua) { return '1'; } +function generateBidsParams(validBidRequests, bidderRequest) { + const bidsArray = []; + + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); + } + + return bidsArray; +} + /** - * Generate query parameters for the request - * @param bid {bid} - * @param bidderRequest {bidderRequest} - * @returns {Object} + * Generate bid specific parameters + * @param {bid} bid + * @param {bidderRequest} bidderRequest + * @returns {Object} bid specific params object */ -function generateParameters(bid, bidderRequest) { +function generateBidParameters(bid, bidderRequest) { const {params} = bid; - const timeout = config.getConfig('bidderTimeout'); - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const [width, height] = getSizes(bid); - const {bidderCode} = bidderRequest; - const domain = window.location.hostname; + 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 requestParams = { + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + 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), + }; + + 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(), - ad_unit_code: getBidIdParameter('adUnitCode', bid), - tmax: timeout, - width: width, - height: height, - publisher_id: params.org, - floor_price: Math.max(getFloor(bid), params.floorPrice), - ua: navigator.userAgent, - bid_id: getBidIdParameter('bidId', bid), - bidder_request_id: getBidIdParameter('bidderRequestId', bid), - transaction_id: getBidIdParameter('transactionId', bid), - session_id: getBidIdParameter('auctionId', bid), + 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) - }; + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + session_id: getBidIdParameter('auctionId', generalObject), + tmax: timeout + } - const userIdsParam = getBidIdParameter('userId', bid); + const userIdsParam = getBidIdParameter('userId', generalObject); if (userIdsParam) { - requestParams.userIds = JSON.stringify(userIdsParam); + generalParams.userIds = JSON.stringify(userIdsParam); } - const ortb2Metadata = config.getConfig('ortb2') || {}; + const ortb2Metadata = bidderRequest.ortb2 || {}; if (ortb2Metadata.site) { - requestParams.site_metadata = JSON.stringify(ortb2Metadata.site); + generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); } if (ortb2Metadata.user) { - requestParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - const playbackMethod = deepAccess(bid, 'mediaTypes.video.playbackmethod'); - if (playbackMethod) { - requestParams.playback_method = playbackMethod; - } - const placement = deepAccess(bid, 'mediaTypes.video.placement'); - if (placement) { - requestParams.placement = placement; - } - const pos = deepAccess(bid, 'mediaTypes.video.pos'); - if (pos) { - requestParams.pos = pos; - } - const minduration = deepAccess(bid, 'mediaTypes.video.minduration'); - if (minduration) { - requestParams.min_duration = minduration; - } - const maxduration = deepAccess(bid, 'mediaTypes.video.maxduration'); - if (maxduration) { - requestParams.max_duration = maxduration; - } - const skip = deepAccess(bid, 'mediaTypes.video.skip'); - if (skip) { - requestParams.skip = skip; - } - const linearity = deepAccess(bid, 'mediaTypes.video.linearity'); - if (linearity) { - requestParams.linearity = linearity; - } - - if (params.placementId) { - requestParams.placement_id = params.placementId; + generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); } if (syncEnabled) { const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); if (allowedSyncMethod) { - requestParams.cs_method = allowedSyncMethod; + generalParams.cs_method = allowedSyncMethod; } } if (bidderRequest.uspConsent) { - requestParams.us_privacy = bidderRequest.uspConsent; + generalParams.us_privacy = bidderRequest.uspConsent; } if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - requestParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - requestParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; } - if (params.ifa) { - requestParams.ifa = params.ifa; + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; } - if (bid.schain) { - requestParams.schain = getSupplyChain(bid.schain); + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); } if (bidderRequest && bidderRequest.refererInfo) { - requestParams.referrer = deepAccess(bidderRequest, 'refererInfo.referer'); - requestParams.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 requestParams; + return generalParams } diff --git a/modules/minutemediaBidAdapter.md b/modules/minutemediaBidAdapter.md index 348cc586e08..70f106a745f 100644 --- a/modules/minutemediaBidAdapter.md +++ b/modules/minutemediaBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to MinuteMedia's demand sources. The MinuteMedia adapter requires setup and approval from the MinuteMedia. Please reach out to hb@minutemedia.com to create an MinuteMedia account. -The adapter supports Video(instream). +The adapter supports Video(instream) & Banner. # Bid Parameters ## Video @@ -27,25 +27,50 @@ The adapter supports Video(instream). # Test Parameters ```javascript -var adUnits = [ - { +var adUnits = [{ code: 'dfp-video-div', - sizes: [[640, 480]], + sizes: [ + [640, 480] + ], mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' - } + video: { + playerSize: [ + [640, 480] + ], + context: 'instream' + } }, bids: [{ - bidder: 'minutemedia', - params: { - org: '56f91cd4d3e3660002000033', // Required - floorPrice: 2.00, // Optional - placementId: '12345678', // Optional - testMode: false // Optional - } + bidder: 'minutemedia', + params: { + org: '56f91cd4d3e3660002000033', // Required + floorPrice: 2.00, // Optional + placementId: 'video-test', // Optional + testMode: false // Optional + } }] - } - ]; + }, + { + code: 'dfp-banner-div', + sizes: [ + [640, 480] + ], + mediaTypes: { + banner: { + sizes: [ + [640, 480] + ] + } + }, + bids: [{ + bidder: 'minutemedia', + params: { + org: '56f91cd4d3e3660002000033', // Required + floorPrice: 2.00, // Optional + placementId: 'banner-test', // Optional + testMode: false // Optional + } + }] + } +]; ``` diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 30749e977a8..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; } @@ -43,10 +44,13 @@ export const spec = { payload.consent_string = bidderRequest.gdprConsent.consentString; 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: ENDPOINT_URL + '?' + formatQS({ t: bidRequest.params.apiKey }), + url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), data: JSON.stringify(payload), }; }); @@ -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 new file mode 100644 index 00000000000..a4af7133370 --- /dev/null +++ b/modules/mobfoxpbBidAdapter.js @@ -0,0 +1,135 @@ +import { isFn, deepAccess, getWindowTop } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'mobfoxpb'; +const AD_URL = 'https://bes.mobfox.com/pbjs'; + +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); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers); + default: + return false; + } +} + +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 (_) { + return 0 + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && bid.params.placementId); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + const winTop = getWindowTop(); + const location = winTop.location; + const placements = []; + const request = { + 'deviceWidth': winTop.screen.width, + 'deviceHeight': winTop.screen.height, + 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + 'secure': 1, + 'host': location.host, + 'page': location.pathname, + 'placements': placements + }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent + } + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + placementId: bid.params.placementId, + bidId: bid.bidId, + schain: bid.schain || {}, + bidfloor: getBidFloor(bid) + }; + const mediaType = bid.mediaTypes + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.traffic = BANNER; + placement.sizes = mediaType[BANNER].sizes; + } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { + placement.traffic = VIDEO; + placement.wPlayer = mediaType[VIDEO].playerSize[0]; + placement.hPlayer = mediaType[VIDEO].playerSize[1]; + placement.playerSize = mediaType[VIDEO].playerSize; + placement.minduration = mediaType[VIDEO].minduration; + placement.maxduration = mediaType[VIDEO].maxduration; + placement.mimes = mediaType[VIDEO].mimes; + placement.protocols = mediaType[VIDEO].protocols; + placement.startdelay = mediaType[VIDEO].startdelay; + placement.placement = mediaType[VIDEO].placement; + placement.skip = mediaType[VIDEO].skip; + placement.skipafter = mediaType[VIDEO].skipafter; + placement.minbitrate = mediaType[VIDEO].minbitrate; + placement.maxbitrate = mediaType[VIDEO].maxbitrate; + placement.delivery = mediaType[VIDEO].delivery; + placement.playbackmethod = mediaType[VIDEO].playbackmethod; + placement.api = mediaType[VIDEO].api; + placement.linearity = mediaType[VIDEO].linearity; + } else if (mediaType && mediaType[NATIVE]) { + placement.traffic = NATIVE; + placement.native = mediaType[NATIVE]; + } + placements.push(placement); + } + + 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)) { + resItem.meta = resItem.meta || {}; + resItem.meta.advertiserDomains = resItem.adomain || []; + + response.push(resItem); + } + } + return response; + }, +}; + +registerBidder(spec); diff --git a/modules/mobfoxpbBidAdapter.md b/modules/mobfoxpbBidAdapter.md index 6eb549919d7..f434b2792a9 100644 --- a/modules/mobfoxpbBidAdapter.md +++ b/modules/mobfoxpbBidAdapter.md @@ -24,7 +24,7 @@ Module that connects to mobfox demand sources { bidder: 'mobfoxpb', params: { - placementId: 0 + placementId: 'testBanner' } } ] @@ -41,7 +41,7 @@ Module that connects to mobfox demand sources { bidder: 'mobfoxpb', params: { - placementId: 0 + placementId: 'testVideo' } } ] @@ -63,7 +63,7 @@ Module that connects to mobfox demand sources { bidder: 'mobfoxpb', params: { - placementId: 0 + placementId: 'testNative' } } ] 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 018baa37461..163e3c20e4b 100644 --- a/modules/my6senseBidAdapter.js +++ b/modules/my6senseBidAdapter.js @@ -1,6 +1,6 @@ import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; -const {registerBidder} = require('../src/adapters/bidderFactory.js'); const BIDDER_CODE = 'my6sense'; const END_POINT = 'https://hb.mynativeplatform.com/pub2/web/v1.15.0/hbwidget.json'; const END_POINT_METHOD = 'POST'; @@ -11,6 +11,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 } 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 c9e6a1f659f..271ecac7aa2 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -12,8 +12,71 @@ const TIME_TO_LIVE = 360 const SUPPORTED_AD_TYPES = [BANNER] +/** + * Keep track of bid data by keys + * @returns {Object} - Map of bid data that can be referenced by multiple keys + */ +export const BidDataMap = () => { + const referenceMap = {} + const bids = [] + + /** + * Add a refence to the index by key value + * @param {String} key - The key to store the index reference + * @param {Integer} index - The index value of the bidData + */ + function addKeyReference(key, index) { + if (!referenceMap.hasOwnProperty(key)) { + referenceMap[key] = index + } + } + + /** + * Adds a bid to the map + * @param {Object} bid - Bid data + * @param {Array/String} keys - Keys to reference the index value + */ + function addBidData(bid, keys) { + const index = bids.length + bids.push(bid) + + if (Array.isArray(keys)) { + keys.forEach((key) => { + addKeyReference(String(key), index) + }) + return + } + + addKeyReference(String(keys), index) + } + + /** + * Get's the bid data refrerenced by the key + * @param {String} key - The key value to find the bid data by + * @returns {Object} - The bid data + */ + function getBidData(key) { + const stringKey = String(key) + if (referenceMap.hasOwnProperty(stringKey)) { + return bids[referenceMap[stringKey]] + } + } + + // Return API + return { + addBidData, + getBidData, + } +} + const bidRequestMap = {} const adUnitsRequested = {} +const extData = {} + +// Filtering +const adsToFilter = new Set() +const advertisersToFilter = new Set() +const campaignsToFilter = new Set() // Prebid adapter referrence doc: https://docs.prebid.org/dev-docs/bidder-adaptor.html @@ -45,7 +108,7 @@ export const spec = { if (!bid.params) return bid.bidder === BIDDER_CODE // Check if any supplied parameters are invalid - const hasInvalidParameters = Object.keys(bid.params).some(key => { + const hasInvalidParameters = Object.keys(bid.params).some((key) => { const value = bid.params[key] const validityCheck = validParameter[key] @@ -69,27 +132,34 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { const placementIds = new Set() - const placmentBidIdMap = {} + const bidDataMap = BidDataMap() + const placementSizes = { length: 0 } let placementId, pageUrl validBidRequests.forEach((request) => { pageUrl = deepAccess( request, 'params.url', - bidderRequest.refererInfo.referer + bidderRequest.refererInfo.page ) placementId = deepAccess(request, 'params.placementId') - if (placementId) { + const bidDataKeys = [request.adUnitCode] + + if (placementId && !placementIds.has(placementId)) { placementIds.add(placementId) + bidDataKeys.push(placementId) + + placementSizes[placementId] = request.sizes + placementSizes.length++ } - var key = placementId || request.adUnitCode - placmentBidIdMap[key] = { + const bidData = { bidId: request.bidId, size: getLargestSize(request.sizes), } + bidDataMap.addBidData(bidData, bidDataKeys) }) - bidRequestMap[bidderRequest.bidderRequestId] = placmentBidIdMap + bidRequestMap[bidderRequest.bidderRequestId] = bidDataMap // Build adUnit data const adUnitData = { @@ -123,6 +193,25 @@ export const spec = { }, ] + // Add filtering + if (adsToFilter.size > 0) { + params.unshift({ key: 'ntv_atf', value: Array.from(adsToFilter).join(',') }) + } + + if (advertisersToFilter.size > 0) { + params.unshift({ key: 'ntv_avtf', value: Array.from(advertisersToFilter).join(',') }) + } + + if (campaignsToFilter.size > 0) { + 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 if (placementIds.size > 0) { // Convert Set to Array (IE 11 Safe) const placements = [] @@ -131,6 +220,7 @@ export const spec = { params.unshift({ key: 'ntv_ptd', value: placements.join(',') }) } + // Add GDPR params if (bidderRequest.gdprConsent) { // Put on the beginning of the qs param array params.unshift({ @@ -139,6 +229,7 @@ export const spec = { }) } + // Add USP params if (bidderRequest.uspConsent) { // Put on the beginning of the qs param array params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent }) @@ -195,6 +286,8 @@ export const spec = { }, } + if (bid.ext) extData[bid.id] = bid.ext + bidResponses.push(bidResponse) }) }) @@ -300,7 +393,15 @@ export const spec = { * Will be called when a bid from the adapter won the auction. * @param {Object} bid - The bid that won the auction */ - onBidWon: function (bid) {}, + onBidWon: function (bid) { + const ext = extData[bid.dealId] + + if (!ext) return + + appendFilterData(adsToFilter, ext.adsToFilter) + appendFilterData(advertisersToFilter, ext.advertisersToFilter) + appendFilterData(campaignsToFilter, ext.campaignsToFilter) + }, /** * Will be called when the adserver targeting has been set for a bid from the adapter. @@ -315,12 +416,14 @@ export const spec = { * @returns {String} - The bidId value associated with the corresponding placementId */ getAdUnitData: function (bidderRequestId, bid) { - var data = deepAccess(bidRequestMap, `${bidderRequestId}.${bid.impid}`) + const bidDataMap = bidRequestMap[bidderRequestId] - if (data) return data + const placementId = bid.impid + const adUnitCode = deepAccess(bid, 'ext.ad_unit_id') - var unitCode = deepAccess(bid, 'ext.ad_unit_id') - return deepAccess(bidRequestMap, `${bidderRequestId}.${unitCode}`) + return ( + bidDataMap.getBidData(adUnitCode) || bidDataMap.getBidData(placementId) + ) }, } registerBidder(spec) @@ -375,3 +478,14 @@ function getLargestSize(sizes, method = area) { * @returns The calculated area */ const area = (size) => size[0] * size[1] + +/** + * Save any filter data from winning bid requests for subsequent requests + * @param {Array} filter - The filter data bucket currently stored + * @param {Array} filterData - The filter data to add + */ +function appendFilterData(filter, filterData) { + if (filterData && Array.isArray(filterData) && filterData.length) { + filterData.forEach((ad) => filter.add(ad)) + } +} diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index 7bd86879e9d..a292b42f144 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -34,16 +34,6 @@ function readnavIDFromCookie() { return storage.cookiesAreEnabled ? (storage.findSimilarCookies('nav') ? storage.findSimilarCookies('nav')[0] : null) : null; } -function readnvgnavFromLocalStorage() { - var i; - const query = '^nvg|^nav'; - for (i in window.localStorage) { - if (i.match(query) || (!query && typeof i === 'string')) { - return storage.getDataFromLocalStorage(i.match(query).input); - } - } -} - /** @type {Submodule} */ export const naveggIdSubmodule = { /** @@ -72,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..ee6ece2b033 --- /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 85537d382c2..375258176da 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,9 +1,10 @@ -import { isStr, _each, getBidIdParameter } from '../src/utils.js'; +import { isStr, _each, parseUrl, getWindowTop, getBidIdParameter } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; +const TEST_ENDPOINT = 'https://test.pbs.nextmillmedia.com/openrtb2/auction'; const SYNC_ENDPOINT = 'https://statics.nextmillmedia.com/load-cookie.html?v=4'; const TIME_TO_LIVE = 360; @@ -13,7 +14,7 @@ export const spec = { isBidRequestValid: function(bid) { return !!( - bid.params.placement_id && isStr(bid.params.placement_id) + (bid.params.placement_id && isStr(bid.params.placement_id)) || (bid.params.group_id && isStr(bid.params.group_id)) ); }, @@ -28,13 +29,17 @@ export const spec = { 'ext': { 'prebid': { 'storedrequest': { - 'id': getBidIdParameter('placement_id', bid.params) + 'id': getPlacementId(bid) } }, + 'nextMillennium': { 'refresh_count': window.nmmRefreshCounts[bid.adUnitCode]++, + 'elOffsets': getBoundingClient(bid), + 'scrollTop': window.pageYOffset || document.documentElement.scrollTop } - } + }, + ...bid.ortb2 } const gdprConsent = bidderRequest && bidderRequest.gdprConsent; @@ -46,10 +51,12 @@ export const spec = { if (uspConsent) { postBody.regs.ext.us_privacy = uspConsent; } + if (gdprConsent) { if (typeof gdprConsent.gdprApplies !== 'undefined') { postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; } + if (typeof gdprConsent.consentString !== 'undefined') { postBody.user = { ext: { consent: gdprConsent.consentString } @@ -58,9 +65,12 @@ export const spec = { } } + const urlParameters = parseUrl(getWindowTop().location.href).search; + const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; + requests.push({ method: 'POST', - url: ENDPOINT, + url: isTest ? TEST_ENDPOINT : ENDPOINT, data: JSON.stringify(postBody), options: { contentType: 'application/json', @@ -91,6 +101,7 @@ export const spec = { meta: { advertiserDomains: bid.adomain || [] }, + ad: bid.adm }); }); @@ -124,5 +135,48 @@ 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) + const placementId = getBidIdParameter('placement_id', bid.params) + if (!groupId) return placementId + + let windowTop = getTopWindow(window) + let size = [] + if (bid.mediaTypes) { + if (bid.mediaTypes.banner) size = bid.mediaTypes.banner.sizes && bid.mediaTypes.banner.sizes[0] + if (bid.mediaTypes.video) size = bid.mediaTypes.video.playerSize + } + + const host = (windowTop && windowTop.location && windowTop.location.host) || '' + return `g${groupId};${size.join('x')};${host}` +} + +function getTopWindow(curWindow, nesting = 0) { + if (nesting > 10) { + return curWindow + } + + try { + if (curWindow.parent.document) { + return getTopWindow(curWindow.parent.window, ++nesting) + } + } catch (err) { + return curWindow + } +} registerBidder(spec); diff --git a/modules/nextMillenniumBidAdapter.md b/modules/nextMillenniumBidAdapter.md index 048fe907ac7..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 @@ -21,8 +21,9 @@ Currently module supports only banner mediaType. bids: [{ bidder: 'nextMillennium', params: { - placement_id: '-1' + placement_id: '-1', + group_id: '6731' } }] }]; -``` \ No newline at end of file +``` diff --git a/modules/nextrollBidAdapter.js b/modules/nextrollBidAdapter.js index 4e82bc1cbda..533c47e1ea6 100644 --- a/modules/nextrollBidAdapter.js +++ b/modules/nextrollBidAdapter.js @@ -39,7 +39,8 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - let topLocation = parseUrl(deepAccess(bidderRequest, 'refererInfo.referer')); + // TODO: is 'page' the right value here? + let topLocation = parseUrl(deepAccess(bidderRequest, 'refererInfo.page')); return validBidRequests.map((bidRequest) => { return { @@ -65,7 +66,6 @@ export const spec = { } }, - user: _getUser(validBidRequests), site: _getSite(bidRequest, topLocation), seller: _getSeller(bidRequest), device: _getDevice(bidRequest), @@ -186,22 +186,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 a59bb635875..ed0c0c66a7a 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -1,17 +1,21 @@ 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} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'nexx360'; -const BIDDER_URL = 'https://fast.nexx360.io/prebid' -const CACHE_URL = 'https://fast.nexx360.io/cache' -const METRICS_TRACKER_URL = 'https://fast.nexx360.io/track-imp' +const BIDDER_URL = 'https://fast.nexx360.io/prebid'; +const CACHE_URL = 'https://fast.nexx360.io/cache'; +const METRICS_TRACKER_URL = 'https://fast.nexx360.io/track-imp'; + +const GVLID = 965; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: ['revenuemaker'], // short code - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -19,6 +23,9 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { + if (!!bid.params.bidfloorCurrency && !['EUR', 'USD'].includes(bid.params.bidfloorCurrency)) return false; + if (!!bid.params.bidfloor && typeof bid.params.bidfloor !== 'number') return false; + if (!!bid.params.keywords && typeof bid.params.keywords !== 'object') return false; return !!(bid.params.account && bid.params.tagId); }, /** @@ -34,20 +41,26 @@ export const spec = { let userEids = null; Object.keys(validBidRequests).forEach(key => { adunitValue = validBidRequests[key]; - adUnits.push({ + const foo = { account: adunitValue.params.account, tagId: adunitValue.params.tagId, + videoExt: adunitValue.params.videoExt, label: adunitValue.adUnitCode, bidId: adunitValue.bidId, auctionId: adunitValue.auctionId, transactionId: adunitValue.transactionId, - mediatypes: adunitValue.mediaTypes - }); + mediatypes: adunitValue.mediaTypes, + bidfloor: adunitValue.params.bidfloor || 0, + bidfloorCurrency: adunitValue.params.bidfloorCurrency || 'USD', + keywords: adunitValue.params.keywords ? transformBidderParamKeywords(adunitValue.params.keywords) : [], + } + adUnits.push(foo); if (adunitValue.userIdAsEids) userEids = adunitValue.userIdAsEids; }); 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) { @@ -77,20 +90,19 @@ export const spec = { */ interpretResponse: function(serverResponse, bidRequest) { const serverBody = serverResponse.body; - // const headerValue = serverResponse.headers.get('some-response-header'); 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'], - adUrl: `${CACHE_URL}?uuid=${value['uuid']}`, ttl: value['ttl'], creativeId: value['creativeId'], netRevenue: true, @@ -105,6 +117,21 @@ export const spec = { } */ }; + 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); }); } @@ -133,7 +160,7 @@ export const spec = { */ onBidWon: function(bid) { // fires a pixel to confirm a winning bid - const params = { type: 'prebid' }; + const params = { type: 'prebid', mediatype: 'banner' }; if (bid.hasOwnProperty('nexx360')) { if (bid.nexx360.hasOwnProperty('ssp')) params.ssp = bid.nexx360.ssp; if (bid.nexx360.hasOwnProperty('tagId')) params.tag_id = bid.nexx360.tagId; diff --git a/modules/nexx360BidAdapter.md b/modules/nexx360BidAdapter.md index 882d83cb24e..532d48418b6 100644 --- a/modules/nexx360BidAdapter.md +++ b/modules/nexx360BidAdapter.md @@ -10,9 +10,13 @@ Maintainer: gabriel@nexx360.io Connects to Nexx360 network for bids. -Nexx360 bid adapter supports Banner only for the time being. +To use us as a bidder you must have an account and an active "tagId" on our Nexx360 platform. # Test Parameters + +## Web + +### Display ``` var adUnits = [ // Banner adUnit @@ -33,3 +37,23 @@ var adUnits = [ }, ]; ``` + +### Video Instream +``` + var videoAdUnit = { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'nexx360', + params: { + account: '1067', + tagId: 'luvxjvgn' + } + }] + }; +``` diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index f788093f833..cfe18301b32 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,15 +26,6 @@ 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) { @@ -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,7 +145,7 @@ 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) { @@ -386,7 +379,7 @@ export const spec = { const endpoint = buildEndpoint(); let options = {}; - if (!nobidHasPurpose1Consent(bidderRequest)) { + if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { options = { withCredentials: false }; } diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index ae9cc4c818f..661a14ca0ef 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -18,6 +18,11 @@ export const novatiqIdSubmodule = { * @type {string} */ name: 'novatiq', + /** + * used to specify vendor id + * @type {number} + */ + gvlid: 1119, /** * decode the stored id value for passing to bid requests @@ -202,7 +207,7 @@ export const novatiqIdSubmodule = { let sharedId = null; if (this.useSharedId(configParams)) { let cookieOrStorageID = this.getCookieOrStorageID(configParams); - const storage = getStorageManager({moduleName: 'pubCommonId'}); + const storage = getStorageManager({gvlid: this.gvlid, moduleName: 'pubCommonId'}); // first check local storage if (storage.hasLocalStorage()) { diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 7d2989b2066..e0630c7e412 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -6,11 +6,34 @@ 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.10'; +const ADAPTER_VERSION = '1.2.13'; + +function getClientWidth() { + const documentElementClientWidth = window.top.document.documentElement.clientWidth + ? window.top.document.documentElement.clientWidth + : 0 + const innerWidth = window.top.innerWidth ? window.top.innerWidth : 0 + const outerWidth = window.top.outerWidth ? window.top.outerWidth : 0 + const screenWidth = window.top.screen.width ? window.top.screen.width : 0 + + return documentElementClientWidth || innerWidth || outerWidth || screenWidth +} + +function getClientHeight() { + const documentElementClientHeight = window.top.document.documentElement.clientHeight + ? window.top.document.documentElement.clientHeight + : 0 + const innerHeight = window.top.innerHeight ? window.top.innerHeight : 0 + const outerHeight = window.top.outerHeight ? window.top.outerHeight : 0 + const screenHeight = window.top.screen.height ? window.top.screen.height : 0 + + return documentElementClientHeight || innerHeight || outerHeight || screenHeight +} function isBidRequestValid(bid) { const adUnitSizes = getAdUnitSizes(bid); @@ -60,6 +83,10 @@ function buildRequests(validBidRequests, bidderRequest) { ext: { adapterversion: ADAPTER_VERSION, prebidversion: '$prebid.version$' + }, + device: { + w: getClientWidth(), + h: getClientHeight() } }; @@ -74,11 +101,12 @@ function buildRequests(validBidRequests, bidderRequest) { if (bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('banner')) { openRtbBidRequestBanner.site.id = bidRequest.params.assetKey; + const floor = getFloor(bidRequest); openRtbBidRequestBanner.imp.push({ id: bidRequest.bidId, tagid: bidRequest.params.adUnitId, - bidfloor: getFloor(bidRequest), + ...(floor && {bidfloor: floor}), banner: { format: sizes }, @@ -171,6 +199,7 @@ function onTimeout(timeoutData) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER], isBidRequestValid, getUserSyncs, 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..0364acd5d21 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -1,13 +1,13 @@ '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 { createEidsArray } from './userId/eids.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/'; @@ -68,6 +68,9 @@ function buildRequests(validBidRequests, bidderRequest) { if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].userId) { payload.userId = createEidsArray(validBidRequests[0].userId); } + if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].schain && isSchainValid(validBidRequests[0].schain)) { + payload.schain = validBidRequests[0].schain; + } try { if (storage.hasLocalStorage()) { payload.onetagSid = storage.getDataFromLocalStorage('onetag_sid'); @@ -245,6 +248,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 +257,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 +270,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 +379,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..6f9d5620d53 100644 --- a/modules/ooloAnalyticsAdapter.js +++ b/modules/ooloAnalyticsAdapter.js @@ -1,4 +1,5 @@ -import { _each, deepClone, pick, deepSetValue, getOrigin, logError, logInfo } from '../src/utils.js'; +import { _each, deepClone, pick, deepSetValue, logError, logInfo } from '../src/utils.js'; +import { getOrigin } from '../libraries/getOrigin/index.js'; import adapter from '../src/AnalyticsAdapter.js' import adapterManager from '../src/adapterManager.js' import CONSTANTS from '../src/constants.json' diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index f515eb14011..f07b37e16e1 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; diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 85dcfbb3b47..7bea38a2de3 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 @@ -259,7 +258,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 +270,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 +318,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,9 +349,6 @@ 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; 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..a7a8d53a624 --- /dev/null +++ b/modules/openxOrtbBidAdapter.js @@ -0,0 +1,349 @@ +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} + }; + enrichImp(imp, bid, floor); + return imp; + }); + return { + method: 'POST', + url: 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.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 interpretResponse(resp, req) { + // 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 = respBody.ext.paf; + response.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); + } + + return response + })]; + }); + + 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..27ca2dc0ed8 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -1,9 +1,21 @@ -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'; const BIDDER_CODE = 'operaads'; @@ -209,8 +221,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 +230,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), @@ -680,23 +691,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/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..bda0a0587f4 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -82,7 +82,7 @@ export const spec = { 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 a0e91a480a2..1230694fc65 100644 --- a/modules/otmBidAdapter.js +++ b/modules/otmBidAdapter.js @@ -1,10 +1,21 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import {logInfo, logError, getBidIdParameter, _each, getValue, isFn, isPlainObject} from '../src/utils.js'; +import { + logInfo, + logError, + getBidIdParameter, + _each, + getValue, + isFn, + isPlainObject, + isArray, + isStr, + isNumber, +} from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'otm'; const OTM_BID_URL = 'https://ssp.otm-r.com/adjson'; -const DEF_CUR = 'RUB' +const DEFAULT_CURRENCY = 'RUB' export const spec = { @@ -19,7 +30,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return !!bid.params.tid; + return Boolean(bid.params.tid); }, /** @@ -33,43 +44,39 @@ export const spec = { logInfo('validBidRequests', validBidRequests); const bidRequests = []; - let tz = new Date().getTimezoneOffset() - let referrer = ''; - if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.referer; - } + const tz = new Date().getTimezoneOffset() + // TODO: are these the right referer values? + const referrer = bidderRequest?.refererInfo?.page || ''; + const topOrigin = bidderRequest?.refererInfo?.domain || ''; _each(validBidRequests, (bid) => { - let domain = getValue(bid.params, 'domain') || '' - let tid = getValue(bid.params, 'tid') - let cur = getValue(bid.params, 'currency') || DEF_CUR - let bidid = getBidIdParameter('bidId', bid) - let transactionid = getBidIdParameter('transactionId', bid) - let auctionid = getBidIdParameter('auctionId', bid) - let bidfloor = _getBidFloor(bid) + const domain = isStr(bid.params.domain) ? bid.params.domain : topOrigin + const cur = getValue(bid.params, 'currency') || DEFAULT_CURRENCY + const bidid = getBidIdParameter('bidId', bid) + const transactionid = getBidIdParameter('transactionId', bid) + const auctionid = getBidIdParameter('auctionId', bid) + const bidfloor = _getBidFloor(bid) _each(bid.sizes, size => { - let width = 0; - let height = 0; - if (size.length && typeof size[0] === 'number' && typeof size[1] === 'number') { - width = size[0]; - height = size[1]; - } + const hasSizes = isArray(size) && isNumber(size[0]) && isNumber(size[1]) + const width = hasSizes ? size[0] : 0; + const height = hasSizes ? size[1] : 0; + bidRequests.push({ method: 'GET', url: OTM_BID_URL, data: { - tz: tz, + tz, w: width, h: height, - domain: domain, + domain, l: referrer, - s: tid, - cur: cur, - bidid: bidid, - transactionid: transactionid, - auctionid: auctionid, - bidfloor: bidfloor, + s: bid.params.tid, + cur, + bidid, + transactionid, + auctionid, + bidfloor, }, }) }) @@ -81,10 +88,9 @@ export const spec = { * Generate response. * * @param serverResponse - * @param request * @returns {[]|*[]} */ - interpretResponse: function (serverResponse, request) { + interpretResponse: function (serverResponse) { logInfo('serverResponse', serverResponse.body); const responsesBody = serverResponse ? serverResponse.body : {}; @@ -102,7 +108,7 @@ export const spec = { width: bid.w, height: bid.h, creativeId: bid.creativeid, - currency: bid.currency || 'RUB', + currency: bid.currency || DEFAULT_CURRENCY, netRevenue: true, ad: bid.ad, ttl: bid.ttl, @@ -132,12 +138,12 @@ function _getBidFloor(bid) { return bid.params.bidfloor ? bid.params.bidfloor : 0; } - let floor = bid.getFloor({ - currency: DEF_CUR, + const floor = bid.getFloor({ + currency: DEFAULT_CURRENCY, mediaType: '*', size: '*' }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEF_CUR) { + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) { return floor.floor; } return 0; diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 439570e976e..a640fa776c9 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -27,14 +27,33 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [ NATIVE, BANNER ], isBidRequestValid: (bid) => { + if (typeof bid.params !== 'object') { + return false; + } + + if (typeof deepAccess(bid, 'params.publisher.id') !== 'string') { + return false; + } + + if (!!bid.params.tagid && typeof bid.params.tagid !== 'string') { + return false; + } + + if (!!bid.params.bcat && (typeof bid.params.bcat !== 'object' || !bid.params.bcat.every(item => typeof item === 'string'))) { + return false; + } + + if (!!bid.params.badv && (typeof bid.params.badv !== 'object' || !bid.params.badv.every(item => typeof item === 'string'))) { + return false; + } + return ( !!config.getConfig('outbrain.bidderUrl') && - !!deepAccess(bid, 'params.publisher.id') && !!(bid.nativeParams || bid.sizes) ); }, buildRequests: (validBidRequests, bidderRequest) => { - const page = bidderRequest.refererInfo.referer; + const page = bidderRequest.refererInfo.page; const ua = navigator.userAgent; const test = setOnAny(validBidRequests, 'params.test'); const publisher = setOnAny(validBidRequests, 'params.publisher'); @@ -67,6 +86,13 @@ export const spec = { } } + if (typeof bid.getFloor === 'function') { + const floor = _getFloor(bid, bid.nativeParams ? NATIVE : BANNER); + if (floor) { + imp.bidfloor = floor; + } + } + return imp; }); @@ -190,7 +216,7 @@ export const spec = { registerBidder(spec); function parseNative(bid) { - const { assets, link, eventtrackers } = JSON.parse(bid.adm); + const { assets, link, privacy, eventtrackers } = JSON.parse(bid.adm); const result = { clickUrl: link.url, clickTrackers: link.clicktrackers || undefined @@ -202,6 +228,9 @@ function parseNative(bid) { result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; } }); + if (privacy) { + result.privacyLink = privacy; + } if (eventtrackers) { result.impressionTrackers = []; eventtrackers.forEach(tracker => { @@ -251,8 +280,8 @@ function getNativeAssets(bid) { if (bidParams.sizes) { const sizes = flatten(bidParams.sizes); - w = sizes[0]; - h = sizes[1]; + w = parseInt(sizes[0], 10); + h = parseInt(sizes[1], 10); } asset[props.name] = { @@ -291,3 +320,15 @@ function transformSizes(requestSizes) { return []; } + +function _getFloor(bid, type) { + const floorInfo = bid.getFloor({ + currency: CURRENCY, + mediaType: type, + size: '*' + }); + if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + return parseFloat(floorInfo.floor); + } + return null; +} 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..69fb3e840a1 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -191,7 +191,7 @@ export const spec = { 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'); + let fpd = bidderRequest.ortb2; if (fpd && deepAccess(fpd, 'user')) { logInfo('added FPD user object'); ozoneRequest.user = fpd.user; 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 c9d22655a31..79c709f9bcc 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -9,7 +9,6 @@ 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 {includes} from '../src/polyfill.js'; const MODULE_NAME = 'permutive' @@ -62,45 +61,51 @@ function getModuleConfig (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') + const transformationConfigs = deepAccess(moduleConfig, 'params.transformations') || [] const segmentData = getSegments(maxSegs) acBidders.forEach(function (bidder) { - const currConfig = bidderConfig[bidder] || {} - const nextConfig = mergeOrtbConfig(currConfig, segmentData) - - config.setBidderConfig({ - bidders: [bidder], - config: nextConfig - }) + const currConfig = { ortb2: bidderOrtb2[bidder] || {} } + const nextConfig = updateOrtbConfig(currConfig, segmentData.ac, transformationConfigs) // ORTB2 uses the `ac` segment IDs + bidderOrtb2[bidder] = nextConfig.ortb2; }) } /** - * Merges segments into existing bidder config + * Updates `user.data` object in existing bidder config with Permutive segments * @param {Object} currConfig - Current bidder config - * @param {Object} segmentData - Segment data + * @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine + * the transformations on user data to include the ORTB2 object + * @param {string[]} segmentIDs - Permutive segment IDs * @return {Object} Merged ortb2 object */ -function mergeOrtbConfig (currConfig, segmentData) { - const segment = segmentData.ac.map(seg => { - return { id: seg } - }) +function updateOrtbConfig (currConfig, segmentIDs, transformationConfigs) { const name = 'permutive.com' + + const permutiveUserData = { + name, + segment: segmentIDs.map(segmentId => ({ id: segmentId })), + } + + const transformedUserData = transformationConfigs + .filter(({ id }) => ortb2UserDataTransformations.hasOwnProperty(id)) + .map(({ id, config }) => ortb2UserDataTransformations[id](permutiveUserData, config)) + const ortbConfig = mergeDeep({}, currConfig) - const currSegments = deepAccess(ortbConfig, 'ortb2.user.data') || [] - const userSegment = currSegments + const currentUserData = deepAccess(ortbConfig, 'ortb2.user.data') || [] + + const updatedUserData = currentUserData .filter(el => el.name !== name) - .concat({ name, segment }) + .concat(permutiveUserData, transformedUserData) - deepSetValue(ortbConfig, 'ortb2.user.data', userSegment) + deepSetValue(ortbConfig, 'ortb2.user.data', updatedUserData) return ortbConfig } @@ -236,11 +241,11 @@ export function getSegments (maxSegs) { ac: [..._pcrprs, ..._ppam, ...legacySegs], rubicon: readSegments('_prubicons'), appnexus: readSegments('_papns'), - gam: readSegments('_pdfps') + gam: readSegments('_pdfps'), } - for (const type in segments) { - segments[type] = segments[type].slice(0, maxSegs) + for (const bidder in segments) { + segments[bidder] = segments[bidder].slice(0, maxSegs) } return segments @@ -260,6 +265,34 @@ function readSegments (key) { } } +const unknownIabSegmentId = '_unknown_' + +/** + * Functions to apply to ORT2B2 `user.data` objects. + * Each function should return an a new object containing a `name`, (optional) `ext` and `segment` + * properties. The result of the each transformation defined here will be appended to the array + * under `user.data` in the bid request. + */ +const ortb2UserDataTransformations = { + iab: (userData, config) => ({ + name: userData.name, + ext: { segtax: config.segtax }, + segment: (userData.segment || []) + .map(segment => ({ id: iabSegmentId(segment.id, config.iabIds) })) + .filter(segment => segment.id !== unknownIabSegmentId) + }) +} + +/** + * Transform a Permutive segment ID into an IAB audience taxonomy ID. + * @param {string} permutiveSegmentId + * @param {Object} iabIds object of mappings between Permutive and IAB segment IDs (key: permutive ID, value: IAB ID) + * @return {string} IAB audience taxonomy ID associated with the Permutive segment ID + */ +function iabSegmentId(permutiveSegmentId, iabIds) { + return iabIds[permutiveSegmentId] || unknownIabSegmentId +} + /** @type {RtdSubmodule} */ export const permutiveSubmodule = { name: MODULE_NAME, @@ -267,12 +300,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 0acd42405d1..5fa6e14a474 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -1,8 +1,11 @@ # Permutive Real-time Data Submodule + 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. ## Usage + Compile the Permutive RTD module into your Prebid build: + ``` gulp build --modules=rtdModule,permutiveRtdProvider ``` @@ -29,25 +32,38 @@ pbjs.setConfig({ ``` ## Supported Bidders + 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 | +| 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. +- **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 -| 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 | | - | -| params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` | -| params.maxSegs | Integer | Maximum number of cohorts to be included in either the `permutive` or `p_standard` key-value. | `500` | + +| 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 | | - | +| params.acBidders | String[] | An array of bidders which should receive AC cohorts. | `[]` | +| 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 + +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 + +| 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) | 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..08ef5406d9f --- /dev/null +++ b/modules/pianoDmpAnalyticsAdapter.js @@ -0,0 +1,38 @@ +import adapter from '../src/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..bffd5badded 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 = {}; @@ -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 d30fd5bf810..4007c67c82f 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -38,6 +38,8 @@ import {find, includes} from '../../src/polyfill.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; @@ -388,18 +390,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; }, []); @@ -441,18 +438,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 @@ -561,14 +559,17 @@ Object.assign(ORTB2.prototype, { const nativeParams = adUnit.nativeParams; let nativeAssets; - if (nativeParams) { + if (FEATURES.NATIVE && 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 + required: params.required ? 1 : 0, + id: (isNumber(params.id)) ? idCounter = params.id : idCounter }, obj ? cleanObj(obj) : {}); } @@ -683,7 +684,7 @@ Object.assign(ORTB2.prototype, { } } - if (nativeAssets) { + if (FEATURES.NATIVE && nativeAssets) { try { mediaTypes['native'] = { request: JSON.stringify({ @@ -712,7 +713,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')}); @@ -749,27 +753,64 @@ 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 getFloorBid = find(firstBidRequest.bids, bid => bid.adUnitCode === adUnit.code && typeof bid.getFloor === 'function'); + 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 + + const convertCurrency = typeof getGlobal().convertCurrency !== 'function' + ? (amount) => amount + : (amount, from, to) => { + if (from === to) return amount; + let result = null; + try { + result = getGlobal().convertCurrency(amount, from, to); + } catch (e) { + } + return result; + } + const s2sCurrency = config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY; + + return adUnit.bids + .map((bid) => this.getBidRequest(imp.id, bid.bidder)) + .map((bid) => { + if (!bid || typeof bid.getFloor !== 'function') return; + try { + const {currency, floor} = bid.getFloor({ + currency: s2sCurrency + }); + return { + currency, + floor: parseFloat(floor) + } + } catch (e) { + logError('PBS: getFloor threw an error: ', e); + } + }) + .reduce((min, floor) => { + // if any bid does not have a valid floor, do not attempt to send any to PBS + if (floor == null || floor.currency == null || floor.floor == null || isNaN(floor.floor)) { + min.min = null; + } + if (min.min === null) { + return min; + } + // otherwise, pick the minimum one (or, in some strange confluence of circumstances, the one in the best currency) + if (min.ref == null) { + min.ref = min.min = floor; + } else { + const value = convertCurrency(floor.floor, floor.currency, min.ref.currency); + if (value != null && value < min.ref.floor) { + min.ref.floor = value; + min.min = floor; + } + } + return min; + }, {}).min + })(); - if (getFloorBid) { - let floorInfo; - try { - floorInfo = getFloorBid.getFloor({ - currency: config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY, - }); - } catch (e) { - logError('PBS: getFloor threw an error: ', e); - } - if (floorInfo && floorInfo.currency && !isNaN(parseFloat(floorInfo.floor))) { - imp.bidfloor = parseFloat(floorInfo.floor); - imp.bidfloorcur = floorInfo.currency - } + if (floor) { + imp.bidfloor = floor.floor; + imp.bidfloorcur = floor.currency } if (imp.banner || imp.video || imp.native) { @@ -802,6 +843,11 @@ Object.assign(ORTB2.prototype, { } }; + // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check + if (typeof deepAccess(firstBidRequest, 'bids.0.floorData') === 'object') { + request.ext.prebid.floors = { enabled: false }; + } + // 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}}) @@ -827,7 +873,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'); @@ -896,10 +942,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; @@ -938,6 +984,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 @@ -991,7 +1044,7 @@ 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; if (typeof bid.adm === 'string') { @@ -1107,16 +1160,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; } @@ -1191,18 +1234,13 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques let { gdprConsent } = getConsentData(bidRequests); const adUnits = deepClone(s2sBidRequest.ad_units); - // at this point ad units should have a size array either directly or mapped so filter for that - const validAdUnits = adUnits.filter(unit => - unit.mediaTypes && (unit.mediaTypes.native || (unit.mediaTypes.banner && unit.mediaTypes.banner.sizes) || (unit.mediaTypes.video && unit.mediaTypes.video.playerSize)) - ); - // in case config.bidders contains invalid bidders, we only process those we sent requests for - const requestedBidders = validAdUnits + const requestedBidders = adUnits .map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(uniques)) - .reduce(flatten) + .reduce(flatten, []) .filter(uniques); - const ortb2 = new ORTB2(s2sBidRequest, bidRequests, validAdUnits, requestedBidders); + const ortb2 = new ORTB2(s2sBidRequest, bidRequests, adUnits, requestedBidders); const request = ortb2.buildRequest(); const requestJson = request && JSON.stringify(request); logInfo('BidRequest: ' + requestJson); diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index 1ac7ba84916..6235b10fa13 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -3,6 +3,7 @@ import {ajaxBuilder} from '../src/ajax.js'; import adapter from '../src/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { getStorageManager } from '../src/storageManager.js'; +import CONSTANTS from '../src/constants.json'; /** * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager @@ -12,7 +13,6 @@ const DEFAULT_EVENT_URL = 'https://endpoint.prebidmanager.com/endpoint' const analyticsType = 'endpoint'; const analyticsName = 'Prebid Manager Analytics: '; -var CONSTANTS = require('../src/constants.json'); let ajax = ajaxBuilder(0); var _VERSION = 1; diff --git a/modules/priceFloors.js b/modules/priceFloors.js index ca6e312dad4..900276e6b4e 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) } @@ -138,10 +142,14 @@ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) let matchingData = { floorMin: floorData.floorMin || 0, - floorRuleValue: floorData.values[matchingRule] || floorData.default, + floorRuleValue: isNaN(floorData.values[matchingRule]) ? floorData.default : floorData.values[matchingRule], matchingData: allPossibleMatches[0], // the first possible match is an "exact" so contains all data relevant for anlaytics adapters matchingRule }; + // use adUnit floorMin as priority! + if (typeof deepAccess(bidObject, 'ortb2Imp.ext.prebid.floorMin') === 'number') { + matchingData.floorMin = bidObject.ortb2Imp.ext.prebid.floorMin; + } matchingData.matchingFloor = Math.max(matchingData.floorMin, matchingData.floorRuleValue); // save for later lookup if needed deepSetValue(floorData, `matchingInputs.${matchingInput}`, {...matchingData}); @@ -743,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/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 f69fb20e5d5..6a73838ff1d 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -83,6 +83,8 @@ function setMediaTypes(types, bid) { function copyRequiredBidDetails(bid) { return pick(bid, [ 'bidder', + 'bidderCode', + 'adapterCode', 'bidId', 'status', () => NO_BID, // default a bid to NO_BID until response is recieved or bid is timed out 'finalSource as source', @@ -90,7 +92,7 @@ function copyRequiredBidDetails(bid) { 'adUnit', () => pick(bid, [ 'adUnitCode', 'transactionId', - 'sizes as dimensions', sizes => sizes.map(sizeToDimensions), + 'sizes as dimensions', sizes => sizes && sizes.map(sizeToDimensions), 'mediaTypes', (types) => setMediaTypes(types, bid) ]) ]); @@ -185,11 +187,11 @@ function getDevicePlatform() { } function getValueForKgpv(bid, adUnitId) { - if (bid.params.regexPattern) { + if (bid.params && bid.params.regexPattern) { return bid.params.regexPattern; } else if (bid.bidResponse && bid.bidResponse.regexPattern) { return bid.bidResponse.regexPattern; - } else if (bid.params.kgpv) { + } else if (bid.params && bid.params.kgpv) { return bid.params.kgpv; } else { return adUnitId; @@ -218,30 +220,31 @@ function getAdDomain(bidResponse) { function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { - let bid = adUnit.bids[bidId]; - partnerBids.push({ - 'pn': getAdapterNameForAlias(bid.bidder), - 'bc': bid.bidder, - 'bidid': bid.bidId, - 'db': bid.bidResponse ? 0 : 1, - 'kgpv': getValueForKgpv(bid, adUnitId), - 'kgpsv': bid.params.kgpv ? bid.params.kgpv : adUnitId, - 'psz': bid.bidResponse ? (bid.bidResponse.dimensions.width + 'x' + bid.bidResponse.dimensions.height) : '0x0', - 'eg': bid.bidResponse ? bid.bidResponse.bidGrossCpmUSD : 0, - 'en': bid.bidResponse ? bid.bidResponse.bidPriceUSD : 0, - 'di': bid.bidResponse ? (bid.bidResponse.dealId || EMPTY_STRING) : EMPTY_STRING, - 'dc': bid.bidResponse ? (bid.bidResponse.dealChannel || EMPTY_STRING) : EMPTY_STRING, - 'l1': bid.bidResponse ? bid.clientLatencyTimeMs : 0, - 'l2': 0, - 'adv': bid.bidResponse ? getAdDomain(bid.bidResponse) || undefined : undefined, - 'ss': (s2sBidders.indexOf(bid.bidder) > -1) ? 1 : 0, - 't': (bid.status == ERROR && bid.error.code == TIMEOUT_ERROR) ? 1 : 0, - 'wb': (highestBid && highestBid.requestId === bid.bidId ? 1 : 0), - 'mi': bid.bidResponse ? (bid.bidResponse.mi || undefined) : undefined, - 'af': bid.bidResponse ? (bid.bidResponse.mediaType || undefined) : undefined, - 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, - 'ocry': bid.bidResponse ? (bid.bidResponse.originalCurrency || CURRENCY_USD) : CURRENCY_USD, - 'piid': bid.bidResponse ? (bid.bidResponse.partnerImpId || EMPTY_STRING) : EMPTY_STRING + adUnit.bids[bidId].forEach(function(bid) { + partnerBids.push({ + 'pn': getAdapterNameForAlias(bid.adapterCode || bid.bidder), + 'bc': bid.bidderCode || bid.bidder, + 'bidid': bid.bidId || bidId, + 'db': bid.bidResponse ? 0 : 1, + 'kgpv': getValueForKgpv(bid, adUnitId), + 'kgpsv': bid.params && bid.params.kgpv ? bid.params.kgpv : adUnitId, + 'psz': bid.bidResponse ? (bid.bidResponse.dimensions.width + 'x' + bid.bidResponse.dimensions.height) : '0x0', + 'eg': bid.bidResponse ? bid.bidResponse.bidGrossCpmUSD : 0, + 'en': bid.bidResponse ? bid.bidResponse.bidPriceUSD : 0, + 'di': bid.bidResponse ? (bid.bidResponse.dealId || EMPTY_STRING) : EMPTY_STRING, + 'dc': bid.bidResponse ? (bid.bidResponse.dealChannel || EMPTY_STRING) : EMPTY_STRING, + 'l1': bid.bidResponse ? bid.clientLatencyTimeMs : 0, + 'l2': 0, + 'adv': bid.bidResponse ? getAdDomain(bid.bidResponse) || undefined : undefined, + 'ss': (s2sBidders.indexOf(bid.bidder) > -1) ? 1 : 0, + 't': (bid.status == ERROR && bid.error.code == TIMEOUT_ERROR) ? 1 : 0, + 'wb': (highestBid && highestBid.adId === bid.adId ? 1 : 0), + 'mi': bid.bidResponse ? (bid.bidResponse.mi || undefined) : undefined, + 'af': bid.bidResponse ? (bid.bidResponse.mediaType || undefined) : undefined, + 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, + 'ocry': bid.bidResponse ? (bid.bidResponse.originalCurrency || CURRENCY_USD) : CURRENCY_USD, + 'piid': bid.bidResponse ? (bid.bidResponse.partnerImpId || EMPTY_STRING) : EMPTY_STRING + }); }); return partnerBids; }, []) @@ -307,8 +310,14 @@ function executeBidsLoggerCall(e, highestCpmBids) { function executeBidWonLoggerCall(auctionId, adUnitId) { const winningBidId = cache.auctions[auctionId].adUnitCodes[adUnitId].bidWon; - const winningBid = cache.auctions[auctionId].adUnitCodes[adUnitId].bids[winningBidId]; - const adapterName = getAdapterNameForAlias(winningBid.bidder); + const winningBids = cache.auctions[auctionId].adUnitCodes[adUnitId].bids[winningBidId]; + let winningBid = winningBids[0]; + + if (winningBids.length > 1) { + winningBid = winningBids.filter(bid => bid.adId === cache.auctions[auctionId].adUnitCodes[adUnitId].bidWonAdId)[0]; + } + + const adapterName = getAdapterNameForAlias(winningBid.adapterCode || winningBid.bidder); let pixelURL = END_POINT_WIN_BID_LOGGER; pixelURL += 'pubid=' + publisherId; pixelURL += '&purl=' + enc(config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''); @@ -319,7 +328,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&pdvid=' + enc(profileVersionId); pixelURL += '&slot=' + enc(adUnitId); pixelURL += '&pn=' + enc(adapterName); - pixelURL += '&bc=' + enc(winningBid.bidder); + pixelURL += '&bc=' + enc(winningBid.bidderCode || winningBid.bidder); pixelURL += '&en=' + enc(winningBid.bidResponse.bidPriceUSD); pixelURL += '&eg=' + enc(winningBid.bidResponse.bidGrossCpmUSD); pixelURL += '&kgpv=' + enc(getValueForKgpv(winningBid, adUnitId)); @@ -349,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; } @@ -362,16 +371,22 @@ function bidRequestedHandler(args) { dimensions: bid.sizes }; } - cache.auctions[args.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId] = copyRequiredBidDetails(bid); + cache.auctions[args.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId] = [copyRequiredBidDetails(bid)]; }) } function bidResponseHandler(args) { - let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId]; + let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId][0]; if (!bid) { logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId); return; } + + if (bid.bidder && args.bidderCode && bid.bidder !== args.bidderCode) { + bid = copyRequiredBidDetails(args); + cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId].push(bid) + } + bid.adId = args.adId; bid.source = formatSource(bid.source || args.source); setBidStatus(bid, args); bid.clientLatencyTimeMs = Date.now() - cache.auctions[args.auctionId].timestamp; @@ -397,6 +412,7 @@ function bidderDoneHandler(args) { function bidWonHandler(args) { let auctionCache = cache.auctions[args.auctionId]; auctionCache.adUnitCodes[args.adUnitCode].bidWon = args.requestId; + auctionCache.adUnitCodes[args.adUnitCode].bidWonAdId = args.adId; executeBidWonLoggerCall(args.auctionId, args.adUnitCode); } @@ -413,7 +429,7 @@ function bidTimeoutHandler(args) { // db = 0 and t = 1 means bidder did respond with a bid but post timeout args.forEach(badBid => { let auctionCache = cache.auctions[badBid.auctionId]; - let bid = auctionCache.adUnitCodes[badBid.adUnitCode].bids[ badBid.bidId || badBid.requestId ]; + let bid = auctionCache.adUnitCodes[badBid.adUnitCode].bids[ badBid.bidId || badBid.requestId ][0]; if (bid) { bid.status = ERROR; bid.error = { diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 0667ac0fc74..e34d58ddd2e 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -3,6 +3,7 @@ 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'; const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; @@ -11,8 +12,6 @@ const USER_SYNC_URL_IFRAME = 'https://ads.pubmatic.com/AdServer/js/user_sync.htm const USER_SYNC_URL_IMAGE = 'https://image8.pubmatic.com/AdServer/ImgSync?p='; const DEFAULT_CURRENCY = 'USD'; const AUCTION_TYPE = 1; -const GROUPM_ALIAS = {code: 'groupm', gvlid: 98}; -const MARKETPLACE_PARTNERS = ['groupm'] const UNDEFINED = undefined; const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; @@ -112,10 +111,6 @@ const dealChannelValues = { 6: 'PMPG' }; -const FLOC_FORMAT = { - 'EID': 1, - 'SEGMENT': 2 -} // BB stands for Blue BillyWig const BB_RENDERER = { bootstrapPlayer: function(bid) { @@ -274,8 +269,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 }; } @@ -800,67 +796,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); } @@ -977,6 +914,25 @@ function _blockedIabCategoriesValidation(payload, blockedIabCategories) { } } +function _allowedIabCategoriesValidation(payload, allowedIabCategories) { + allowedIabCategories = allowedIabCategories + .filter(function(category) { + if (typeof category === 'string') { // returns only strings + return true; + } else { + logWarn(LOG_WARN_PREFIX + 'acat: Each category should be a string, ignoring category: ' + category); + return false; + } + }) + .map(category => category.trim()) // trim all categories + .filter((category, index, arr) => arr.indexOf(category) === index); // return unique values only + + if (allowedIabCategories.length > 0) { + logWarn(LOG_WARN_PREFIX + 'acat: Selected: ', allowedIabCategories); + payload.ext.acat = allowedIabCategories; + } +} + function _assignRenderer(newBid, request) { let bidParams, context, adUnitCode; if (request.bidderRequest && request.bidderRequest.bids) { @@ -1007,7 +963,6 @@ export const spec = { code: BIDDER_CODE, gvlid: 76, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - aliases: [GROUPM_ALIAS], /** * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid * @@ -1066,12 +1021,6 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: (validBidRequests, bidderRequest) => { - if (bidderRequest && MARKETPLACE_PARTNERS.includes(bidderRequest.bidderCode)) { - // We have got the buildRequests function call for Marketplace Partners - logInfo('For all publishers using ' + bidderRequest.bidderCode + ' bidder, the PubMatic bidder will also be enabled so PubMatic server will respond back with the bids that needs to be submitted for PubMatic and ' + bidderRequest.bidderCode + ' in the network call sent by PubMatic bidder. Hence we do not want to create a network call for ' + bidderRequest.bidderCode + '. This way we are trying to save a network call from browser.'); - return; - } - var refererInfo; if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; @@ -1082,6 +1031,7 @@ export const spec = { var dctrArr = []; var bid; var blockedIabCategories = []; + var allowedIabCategories = []; validBidRequests.forEach(originalBid => { bid = deepClone(originalBid); @@ -1113,6 +1063,9 @@ export const spec = { if (bid.params.hasOwnProperty('bcat') && isArray(bid.params.bcat)) { blockedIabCategories = blockedIabCategories.concat(bid.params.bcat); } + if (bid.params.hasOwnProperty('acat') && isArray(bid.params.acat)) { + allowedIabCategories = allowedIabCategories.concat(bid.params.acat); + } var impObj = _createImpressionObject(bid, conf); if (impObj) { payload.imp.push(impObj); @@ -1133,6 +1086,10 @@ 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'); + } payload.user.gender = (conf.gender ? conf.gender.trim() : UNDEFINED); payload.user.geo = {}; payload.user.geo.lat = _parseSlotParam('lat', conf.lat); @@ -1182,17 +1139,25 @@ export const spec = { } _handleEids(payload, validBidRequests); - _blockedIabCategoriesValidation(payload, blockedIabCategories); - _handleFlocId(payload, validBidRequests); + // First Party Data - const commonFpd = config.getConfig('ortb2') || {}; + const commonFpd = (bidderRequest && bidderRequest.ortb2) || {}; if (commonFpd.site) { mergeDeep(payload, {site: commonFpd.site}); } if (commonFpd.user) { mergeDeep(payload, {user: commonFpd.user}); } - + if (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); + } else if (allowedIabCategories.length) { + _allowedIabCategoriesValidation(payload, allowedIabCategories); + } + _blockedIabCategoriesValidation(payload, blockedIabCategories); // 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') { @@ -1296,9 +1261,8 @@ export const spec = { // if from the server-response the bid.ext.marketplace is set then // submit the bid to Prebid as marketplace name - if (bid.ext && !!bid.ext.marketplace && MARKETPLACE_PARTNERS.includes(bid.ext.marketplace)) { + if (bid.ext && !!bid.ext.marketplace) { newBid.bidderCode = bid.ext.marketplace; - newBid.bidder = bid.ext.marketplace; } bidResponses.push(newBid); diff --git a/modules/pubnxBidAdapter.md b/modules/pubnxBidAdapter.md deleted file mode 100644 index 6c843322402..00000000000 --- a/modules/pubnxBidAdapter.md +++ /dev/null @@ -1,31 +0,0 @@ -# Overview - -``` -Module Name: PubNX Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid-team@pubnx.com -``` - -# Description - -Connects to PubNX exchange for bids. -PubNX Bidder adapter supports Banner ads. -Use bidder code ```pubnx``` for all PubNX traffic. - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - sizes: [[300, 250], [300,600]], // a display size(s) - bids: [{ - bidder: 'pubnx', - params: { - placementId: 'PNX-HB-G396432V4809F3' - } - }] - }, -]; -``` - diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index 006d6da7eb7..a3061eb1c99 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -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') { diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index a1b9ffb56a0..1a3adf12bd8 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -528,8 +528,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/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 669bd062206..3f35cd8ff79 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -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, diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 7aa3ad6088c..0bff10bc6be 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -1,7 +1,7 @@ /* 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, @@ -45,7 +45,7 @@ export const spec = { 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), @@ -330,8 +330,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 +357,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. */ @@ -419,61 +409,14 @@ function user(bidRequest, bidderRequest) { } } if (bidRequest) { - if (bidRequest.userId) { - ext.eids = []; - addExternalUserId(ext.eids, bidRequest.userId.pubcid, 'pubcid.org'); - addExternalUserId(ext.eids, bidRequest.userId.britepoolid, 'britepool.com'); - addExternalUserId(ext.eids, bidRequest.userId.criteoId, 'criteo.com'); - addExternalUserId(ext.eids, bidRequest.userId.idl_env, 'liveramp.com'); - addExternalUserId(ext.eids, deepAccess(bidRequest, 'userId.id5id.uid'), 'id5-sync.com', deepAccess(bidRequest, 'userId.id5id.ext')); - addExternalUserId(ext.eids, deepAccess(bidRequest, 'userId.parrableId.eid'), 'parrable.com'); - addExternalUserId(ext.eids, bidRequest.userId.fabrickId, 'neustar.biz'); - addExternalUserId(ext.eids, deepAccess(bidRequest, 'userId.haloId.haloId'), 'audigent.com'); - addExternalUserId(ext.eids, bidRequest.userId.merkleId, 'merkleinc.com'); - addExternalUserId(ext.eids, bidRequest.userId.lotamePanoramaId, 'crwdcntrl.net'); - addExternalUserId(ext.eids, bidRequest.userId.connectid, 'verizonmedia.com'); - addExternalUserId(ext.eids, deepAccess(bidRequest, 'userId.uid2.id'), 'uidapi.com'); - // liveintent - if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { - addExternalUserId(ext.eids, bidRequest.userId.lipb.lipbid, 'liveintent.com'); - } - // TTD - addExternalUserId(ext.eids, bidRequest.userId.tdid, 'adserver.org', { - rtiPartner: 'TDID' - }); - // digitrust - const digitrustResponse = bidRequest.userId.digitrustid; - if (digitrustResponse && digitrustResponse.data) { - var digitrust = {}; - if (digitrustResponse.data.id) { - digitrust.id = digitrustResponse.data.id; - } - if (digitrustResponse.data.keyv) { - digitrust.keyv = digitrustResponse.data.keyv; - } - ext.digitrust = digitrust; - } + let eids = bidRequest.userIdAsEids; + if (eids) { + ext.eids = eids; } } return { ext }; } -/** - * Produces external userid object in ortb 3.0 model. - */ -function addExternalUserId(eids, id, source, uidExt) { - if (id) { - var uid = { id }; - if (uidExt) { - uid.ext = uidExt; - } - eids.push({ - source, - uids: [ uid ] - }); - } -} - /** * Produces the regulations ortb 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..b803096cd31 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; } 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..938fd566159 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; @@ -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..909b6a7b795 --- /dev/null +++ b/modules/rasBidAdapter.js @@ -0,0 +1,151 @@ +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 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)}`; + } + } + 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..5cf75c3446d --- /dev/null +++ b/modules/rasBidAdapter.md @@ -0,0 +1,50 @@ +# 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"` +| 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..be3f246232e 100644 --- a/modules/readpeakBidAdapter.js +++ b/modules/readpeakBidAdapter.js @@ -1,4 +1,4 @@ -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'; @@ -64,8 +64,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 +111,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 +247,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 +258,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/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 db381555ef9..880e826d75b 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, logWarn, getBidIdParameter, parseQueryStringParameters, triggerPixel, generateUUID, isArray } from '../src/utils.js'; +import { deepAccess, logWarn, getBidIdParameter, parseQueryStringParameters, triggerPixel, generateUUID, isArray, isNumber } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; @@ -6,7 +6,7 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'relaido'; const BIDDER_DOMAIN = 'api.relaido.jp'; -const ADAPTER_VERSION = '1.0.7'; +const ADAPTER_VERSION = '1.0.8'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; @@ -44,7 +44,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; @@ -101,7 +104,8 @@ function buildRequests(validBidRequests, bidderRequest) { uuid: getUuid(), pv: '$prebid.version$', imuid: imuid, - ref: bidderRequest.refererInfo.referer + // TODO: is 'page' the right value here? + ref: bidderRequest.refererInfo.page }) return { @@ -151,9 +155,6 @@ function interpretResponse(serverResponse, bidRequest) { } bidResponses.push(bidResponse); } - - // eslint-disable-next-line no-console - console.log(JSON.stringify(bidResponses)); return bidResponses; } @@ -255,7 +256,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') { @@ -302,6 +306,15 @@ 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]]; + } } } } 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..3787c23b8ea 100644 --- a/modules/revcontentBidAdapter.js +++ b/modules/revcontentBidAdapter.js @@ -4,6 +4,7 @@ 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'; const BIDDER_CODE = 'revcontent'; const NATIVE_PARAMS = { @@ -44,11 +45,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 +197,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)) { 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..564b67641a2 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; diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index a8ea023d46a..504de2e209a 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -287,6 +287,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), }; @@ -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,10 @@ 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 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/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..b3e8e80326e 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -1,4 +1,5 @@ -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'; @@ -180,7 +181,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..ca0d0c1fe65 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.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 afb95d56d69..192d5406b86 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -253,6 +253,12 @@ export const spec = { if (!isNaN(bidFloor)) { data.imp[0].bidfloor = bidFloor; } + + // If the price floors module is active, then we need to signal to PBS! If floorData obj is present is best way to check + if (typeof bidRequest.floorData === 'object') { + data.ext.prebid.floors = { enabled: false }; + } + // if value is set, will overwrite with same value data.imp[0].ext[bidRequest.bidder].video.size_id = determineRubiconVideoSizeId(bidRequest) @@ -311,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`, @@ -830,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; } @@ -1008,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]}; @@ -1025,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(); @@ -1054,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) } }); @@ -1072,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/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index b7aec0f8881..1b178b59728 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -7,8 +7,9 @@ import { _map, deepSetValue, isEmpty, deepAccess } from '../src/utils.js'; import { config } from '../src/config.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=nativendo'; +const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; const NATIVE_ASSET_IDS = {0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon'}; @@ -52,6 +53,8 @@ const NATIVE_PARAMS = { export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, + supportedMediaTypes: [NATIVE], isBidRequestValid: function(bid) { @@ -62,7 +65,7 @@ export const spec = { 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) => { @@ -124,7 +127,8 @@ export const spec = { user: {}, regs: { ext: { - gdpr: 0 + gdpr: 0, + pb_ver: '$prebid.version$' } } }; @@ -187,20 +191,23 @@ 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\}/, bid.price); + link.clicktrackers[index] = clicktracker.replace(/\$\{AUCTION_PRICE\}/g, bid.price); }); } + if (imptrackers) { imptrackers.forEach(function (imptracker, index) { - imptrackers[index] = imptracker.replace(/\$\{AUCTION_PRICE\}/, bid.price); + imptrackers[index] = imptracker.replace(/\$\{AUCTION_PRICE\}/g, bid.price); }); } const result = { - url: link.url, - clickUrl: link.url, + url: clickUrl, + clickUrl: clickUrl, clickTrackers: link.clicktrackers || undefined, impressionTrackers: imptrackers || undefined }; diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index bae27d41028..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 ( @@ -154,9 +184,12 @@ export function getTimeoutUrl (data) { isArray(data[0].params) && data[0].params[0] ) { const params = data[0].params[0]; + const timeout = data[0].timeout + queryParams = '?publisherToken=' + params.publisherId + - '&adUnitId=' + params.adUnitId; + '&adUnitId=' + params.adUnitId + + '&timeout=' + timeout; } return SEEDTAG_SSP_ONTIMEOUT_ENDPOINT + queryParams; } @@ -186,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) { @@ -201,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/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/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 1dd95812e12..731fc5c3a4e 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.1.1'; 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 || [], + bcat: deepAccess(bidderRequest.ortb2Imp, 'bcat') || bidRequests[0].params.bcat || [], 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..923ecec7e4b --- /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..c1987a32c80 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -30,7 +30,7 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { let adUnits = []; - const pageURL = validBidRequests[0].params.contentPageUrl || bidderRequest.refererInfo.referer; + const pageURL = validBidRequests[0].params.contentPageUrl || bidderRequest.refererInfo.page; const isStage = !!validBidRequests[0].params.stage; const isOutstream = deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream'; const isCustomRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.customRender'); diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 182ff384fef..532d938375e 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,58 +35,61 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, sendWithCredentials = false; gdprApplies = null; tcString = ''; - } else if (getGlobal().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 + } 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; } // default global endpoint is cookie-based if no rules falls into cookieless or consent has been given or GDPR doesn't apply + if (!sirdataDomain || !gdprApplies || (deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) { sirdataDomain = 'sddan.com'; sendWithCredentials = true; } - - var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().referer; - - 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=' + actualUrl : ''); - ajax(url, { - success: function (response, req) { - if (req.status === 200) { - try { - const data = JSON.parse(response); - if (data && data.segments) { - addSegmentData(adUnits, data, moduleConfig, onDone); - } else { + // 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) : ''); + + ajax(url, + { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + if (data && data.segments) { + addSegmentData(reqBidsConfigObj, data, moduleConfig, onDone); + } else { + onDone(); + } + } catch (e) { onDone(); + logError('unable to parse Sirdata data' + e); } - } catch (e) { + } else if (req.status === 204) { onDone(); - logError('unable to parse Sirdata data' + e); } - } else if (req.status === 204) { + }, + error: function () { onDone(); + logError('unable to get Sirdata data'); } }, - error: function () { - onDone(); - logError('unable to get Sirdata data'); - } - }, - null, - { - contentType: 'text/plain', - method: 'GET', - withCredentials: sendWithCredentials, - referrerPolicy: 'unsafe-url', - crossOrigin: true - }); + null, + { + contentType: 'text/plain', + method: 'GET', + withCredentials: sendWithCredentials, + referrerPolicy: 'unsafe-url', + crossOrigin: true + }); } -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 || {}); } @@ -96,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) @@ -106,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 || {}); } @@ -117,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) @@ -127,12 +126,14 @@ export function setBidderOrtb2(bidder, segments, categories) { return true; } -export function loadCustomFunction (todo, adUnit, list, data, bid) { +export function loadCustomFunction(todo, adUnit, list, data, bid) { try { if (typeof todo == 'function') { todo(adUnit, list, data, bid); } - } catch (e) { logError(e); } + } catch (e) { + logError(e); + } return true; } @@ -142,32 +143,40 @@ export function getSegAndCatsArray(data, minScore) { try { if (data && data.contextual_categories) { for (let catId in data.contextual_categories) { - let value = data.contextual_categories[catId]; - if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) { - sirdataData.categories.push(catId.toString()); + if (data.contextual_categories.hasOwnProperty(catId)) { + let value = data.contextual_categories[catId]; + if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) { + sirdataData.categories.push(catId.toString()); + } } } } - } catch (e) { logError(e); } + } catch (e) { + logError(e); + } try { if (data && data.segments) { for (let segId in data.segments) { - sirdataData.segments.push(data.segments[segId].toString()); + if (data.segments.hasOwnProperty(segId)) { + sirdataData.segments.push(data.segments[segId].toString()); + } } } - } catch (e) { logError(e); } + } catch (e) { + logError(e); + } 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; var sirdataData = getSegAndCatsArray(data, globalMinScore); - if (!sirdataData || (sirdataData.segments.length < 1 && sirdataData.categories.length < 1)) { logError('no cats'); onDone(); return adUnits; } - const sirdataList = sirdataData.segments.concat(sirdataData.categories); + var sirdataMergedList = []; var curationData = {'segments': [], 'categories': []}; var curationId = '1'; @@ -175,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 @@ -186,12 +195,15 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], globalMinScore); } - window.googletag.pubads().getSlots().forEach(function(n) { - if (typeof n.setTargeting !== 'undefined') { - n.setTargeting('sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories)); + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + window.googletag.pubads().getSlots().forEach(function (n) { + if (typeof n.setTargeting !== 'undefined' && sirdataMergedList && sirdataMergedList.length > 0) { + n.setTargeting('sd_rtd', sirdataMergedList); } }) - } catch (e) { logError(e); } + } catch (e) { + logError(e); + } } // Bid targeting level for FPD non-generic biders @@ -204,10 +216,14 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { } adUnit.hasOwnProperty('bids') && adUnit.bids.forEach(bid => { - bidderIndex = (moduleConfig.params.hasOwnProperty('bidders') ? findIndex(moduleConfig.params.bidders, function(i) { return i.bidder === bid.bidder; }) : false); + bidderIndex = (moduleConfig.params.hasOwnProperty('bidders') ? findIndex(moduleConfig.params.bidders, function (i) { + return i.bidder === bid.bidder; + }) : false); indexFound = (!!(typeof bidderIndex == 'number' && bidderIndex >= 0)); try { curationData = {'segments': [], 'categories': []}; + sirdataMergedList = []; + 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))) { @@ -233,10 +249,13 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); } - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); - } else { - deepSetValue(bid, 'params.keywords.sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories)); + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + deepSetValue(bid, 'params.keywords.sd_rtd', sirdataMergedList); + } } break; @@ -251,15 +270,18 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); } - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); - } else { - sirdataList.concat(curationData.segments).concat(curationData.categories).forEach(function(entry) { - if (target.indexOf('sd_rtd=' + entry) === -1) { - target.push('sd_rtd=' + entry); - } - }); - deepSetValue(bid, 'params.target', target.join(';')); + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + sirdataMergedList.forEach(function (entry) { + if (target.indexOf('sd_rtd=' + entry) === -1) { + target.push('sd_rtd=' + entry); + } + }); + deepSetValue(bid, 'params.target', target.join(';')); + } } break; @@ -269,35 +291,41 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); } - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); - } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + 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'); if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); } - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); - } else { - var cappIxCategories = []; - var ixLength = 0; - var ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); - // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters - sirdataList.concat(curationData.segments).concat(curationData.categories).forEach(function(entry) { - if (ixLength < ixLimit) { - cappIxCategories.push(entry); - ixLength += entry.toString().length; - } - }); - getGlobal().setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + var cappIxCategories = []; + var ixLength = 0; + var ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000); + // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters + sirdataMergedList.forEach(function (entry) { + if (ixLength < ixLimit) { + cappIxCategories.push(entry); + ixLength += entry.toString().length; + } + }); + config.setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}}); + } } } break; @@ -310,10 +338,16 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { } else { data.shared_taxonomy[curationId] = {contextual_categories: {}}; } - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); - } else { - deepSetValue(bid, 'ortb2.user.ext.data', {segments: sirdataData.segments.concat(curationData.segments), contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[curationId].contextual_categories}}); + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + deepSetValue(bid, 'ortb2.user.ext.data', { + segments: sirdataData.segments.concat(curationData.segments), + contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[curationId].contextual_categories} + }); + } } break; @@ -323,10 +357,13 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); } - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); - } else { - setBidderOrtb2(bid.bidder, sirdataList.concat(curationData.segments).concat(curationData.categories), sirdataList.concat(curationData.segments).concat(curationData.categories)); + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + } } break; @@ -336,10 +373,13 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); } - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); - } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + } } break; @@ -350,10 +390,13 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); } - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); - } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + } } break; @@ -363,10 +406,61 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); } - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid); - } else { - setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories)); + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + } + } + break; + + case 'yahoossp': + // For curation Yahoo is pid 30339 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30339'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + } + } + break; + + case 'openx': + // For curation OpenX is pid 30342 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30342'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + } + } + break; + + case 'pubmatic': + // For curation Pubmatic is pid 30345 + curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '30345'); + if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { + curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore); + } + sirdataMergedList = sirdataList.concat(curationData.segments).concat(curationData.categories); + if (sirdataMergedList && sirdataMergedList.length > 0) { + if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { + loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataMergedList, data, bid); + } else { + setBidderOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, data.segments.concat(curationData.segments), sirdataMergedList); + } } break; @@ -381,7 +475,9 @@ export function addSegmentData(adUnits, data, moduleConfig, onDone) { } } } - } catch (e) { logError(e) } + } catch (e) { + logError(e) + } }) }); diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 405799813eb..4a1518ab72e 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -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 2d35e09d777..a04a2fda89a 100644 --- a/modules/slimcutBidAdapter.js +++ b/modules/slimcutBidAdapter.js @@ -9,8 +9,8 @@ const BIDDER_CODE = 'slimcut'; const ENDPOINT_URL = 'https://sb.freeskreen.com/pbr'; export const spec = { code: BIDDER_CODE, - gvlid: 52, - aliases: [{ code: 'scm', gvlid: 52 }], + gvlid: 102, + aliases: [{ code: 'scm', gvlid: 102 }], supportedMediaTypes: ['video', 'banner'], /** * Determines whether or not the given bid request is valid. @@ -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..a24f296102f 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); 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..e15b4c488f4 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -100,7 +100,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); 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..f3188c1f110 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) { @@ -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) { + coreSetup: {}, + layoutSettings: {}, + onCappedCallback: function() { try { - window.sc_smartIntxtStart(n); - } catch (f) {} - }, - onCappedCallback: function (m, n) { - 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..5125eacc716 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -35,8 +35,9 @@ export const spec = { buildRequests: (validBidRequests = [], bidderRequest) => { 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/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/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index c5fc07320d8..5309dbdad5a 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -56,15 +56,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 +84,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 +95,7 @@ export const spec = { }; - const fpd = config.getConfig('ortb2'); + const fpd = bidderRequest.ortb2; if (fpd) { payload.fpd = JSON.stringify(fpd); @@ -272,7 +281,7 @@ export const spec = { }); }); } - } catch (e) {} + } catch (e) { } return syncs; } }; @@ -285,7 +294,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 +312,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 +332,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 +360,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..8a5b816ef43 100644 --- a/modules/sovrnAnalyticsAdapter.js +++ b/modules/sovrnAnalyticsAdapter.js @@ -5,6 +5,7 @@ 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 4ca8f03c6b4..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'), @@ -30,6 +43,14 @@ const ORTB_VIDEO_PARAMS = { 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) } +const REQUIRED_VIDEO_PARAMS = { + context: (value) => value !== ADPOD, + mimes: ORTB_VIDEO_PARAMS.mimes, + minduration: ORTB_VIDEO_PARAMS.minduration, + maxduration: ORTB_VIDEO_PARAMS.maxduration, + protocols: ORTB_VIDEO_PARAMS.protocols +} + export const spec = { code: 'sovrn', supportedMediaTypes: [BANNER, VIDEO], @@ -40,19 +61,25 @@ export const spec = { * @param {object} bid the Sovrn bid to validate * @return boolean for whether or not a bid is valid */ - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { + const video = bid?.mediaTypes?.video return !!( bid.params.tagid && !isNaN(parseFloat(bid.params.tagid)) && - isFinite(bid.params.tagid) && - deepAccess(bid, 'mediaTypes.video.context') !== ADPOD + isFinite(bid.params.tagid) && ( + !video || ( + Object.keys(REQUIRED_VIDEO_PARAMS) + .every(key => REQUIRED_VIDEO_PARAMS[key](video[key])) + ) + ) ) }, /** * Format the bid request object for our endpoint - * @param {BidRequest[]} bidRequests Array of Sovrn bidders * @return object of parameters for Prebid AJAX request + * @param bidReqs + * @param bidderRequest */ buildRequests: function(bidReqs, bidderRequest) { try { @@ -60,7 +87,6 @@ export const spec = { let iv; let schain; let eids; - let tpid = [] let criteoId; _each(bidReqs, function (bid) { @@ -71,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}) } }) } @@ -121,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(), @@ -153,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) } @@ -179,14 +202,12 @@ export const spec = { * @return {Bid[]} An array of formatted bids. */ interpretResponse: function({ body: {id, seatbid} }) { + if (!id || !seatbid || !Array.isArray(seatbid)) return [] + try { - let sovrnBidResponses = []; - if (id && - seatbid && - seatbid.length > 0 && - seatbid[0].bid && - seatbid[0].bid.length > 0) { - seatbid[0].bid.map(sovrnBid => { + return seatbid + .filter(seat => seat) + .map(seat => seat.bid.map(sovrnBid => { const bid = { requestId: sovrnBid.impid, cpm: parseFloat(sovrnBid.price), @@ -196,23 +217,23 @@ export const spec = { dealId: sovrnBid.dealid || null, currency: 'USD', netRevenue: true, - ttl: sovrnBid.ext ? (sovrnBid.ext.ttl || 90) : 90, + mediaType: sovrnBid.nurl ? BANNER : VIDEO, + ttl: sovrnBid.ext?.ttl || 90, meta: { advertiserDomains: sovrnBid && sovrnBid.adomain ? sovrnBid.adomain : [] } } - if (!sovrnBid.nurl) { - bid.mediaType = VIDEO - bid.vastXml = decodeURIComponent(sovrnBid.adm) - } else { - bid.mediaType = BANNER + if (sovrnBid.nurl) { bid.ad = decodeURIComponent(`${sovrnBid.adm}`) + } else { + bid.vastXml = decodeURIComponent(sovrnBid.adm) } - sovrnBidResponses.push(bid); - }); - } - return sovrnBidResponses + + return bid + })) + .flat() } catch (e) { - logError('Could not intrepret bidresponse, error deatils:', e); + logError('Could not interpret bidresponse, error details:', e) + return e } }, @@ -256,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 2fd403058d1..86a37e97e2f 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -11,7 +11,7 @@ export const GOOGLE_CONSENT = { consented_providers: ['3', '7', '11', '12', '15' export const spec = { code: BIDDER_CODE, - gvlid: 52, + gvlid: 165, supportedMediaTypes: [VIDEO], /** @@ -68,7 +68,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 +77,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 +193,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) { diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index e46073a63ba..89bf4bb331d 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -1,5 +1,6 @@ -import {deepAccess, isArray, logWarn, parseUrl} 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'; @@ -11,7 +12,8 @@ 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.41'; +const BIDDER_VERSION = '5.6'; +const DEFAULT_CURRENCY = 'PLN'; const W = window; const { navigator } = W; const oneCodeDetection = {}; @@ -20,6 +22,25 @@ const adSizesCalled = {}; const pageView = {}; var consentApiVersion; +/** + * Get preferred language of browser (i.e. user) + * @returns {string} languageCode - ISO language code + */ +const getBrowserLanguage = () => navigator.language || (navigator.languages && navigator.languages[0]); + +/** + * Get language of top level html object + * @returns {string} languageCode - ISO language code + */ +const getContentLanguage = () => { + try { + const topWindow = getWindowTop(); + return topWindow.document.body.parentNode.lang; + } catch (err) { + logWarn('Could not read language form top-level html', err); + } +}; + /** * Get bid parameters for notification * @param {*} bidData - bid (bidWon), or array of bids (timeout) @@ -28,38 +49,50 @@ const getNotificationPayload = bidData => { if (bidData) { const bids = isArray(bidData) ? bidData : [bidData]; if (bids.length > 0) { - const result = { + let result = { requestId: undefined, siteId: [], slotId: [], tagid: [], } bids.forEach(bid => { - let params = isArray(bid.params) ? bid.params[0] : bid.params; + const { adUnitCode, auctionId, cpm, creativeId, meta, params: bidParams, requestId, timeout } = bid; + let params = isArray(bidParams) ? bidParams[0] : bidParams; params = params || {}; - // check for stored detection - if (oneCodeDetection[bid.requestId]) { - params.siteId = oneCodeDetection[bid.requestId][0]; - params.id = oneCodeDetection[bid.requestId][1]; + // basic notification data + const bidBasicData = { + requestId: auctionId || result.requestId, + timeout: timeout || result.timeout, + pvid: pageView.id, } + result = { ...result, ...bidBasicData } + result.tagid.push(adUnitCode); + + // check for stored detection + if (oneCodeDetection[requestId]) { + params.siteId = oneCodeDetection[requestId][0]; + params.id = oneCodeDetection[requestId][1]; + } if (params.siteId) { result.siteId.push(params.siteId); } if (params.id) { result.slotId.push(params.id); } - if (bid.cpm) { - const meta = bid.meta || {}; - result.cpm = bid.cpm; - result.creativeId = bid.creativeId; - result.adomain = meta.advertiserDomains && meta.advertiserDomains[0]; - result.networkName = meta.networkName; + + if (cpm) { + // non-empty bid data + const bidNonEmptyData = { + cpm, + cpmpl: meta && meta.pricepl, + creativeId, + adomain: meta && meta.advertiserDomains && meta.advertiserDomains[0], + networkName: meta && meta.networkName, + } + result = { ...result, ...bidNonEmptyData } } - result.tagid.push(bid.adUnitCode); - result.requestId = bid.auctionId || result.requestId; - result.timeout = bid.timeout || result.timeout; }) return result; } @@ -97,7 +130,7 @@ const applyClientHints = ortbRequest => { */ if (!pageView.id || location.pathname !== pageView.path) { pageView.path = location.pathname; - pageView.id = Math.floor(1E20 * Math.random()); + pageView.id = Math.floor(1E20 * Math.random()).toString(); } Object.keys(hints).forEach(key => { @@ -120,7 +153,7 @@ const applyClientHints = ortbRequest => { name: 'pvid', segment: [ { - value: `${pageView.id}` + value: pageView.id } ] }]; @@ -151,6 +184,12 @@ const applyGdpr = (bidderRequest, ortbRequest) => { } } +/** + * Get currency (either default or adserver) + * @returns {string} currency name + */ +const getCurrency = () => config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; + /** * Get value for first occurence of key within the collection */ @@ -260,12 +299,13 @@ const mapNative = slot => { return assets ? { request: JSON.stringify({ native: { assets } }) } : undefined; } -var mapVideo = slot => { - var video = deepAccess(slot, 'mediaTypes.video'); - var videoParamsUsed = ['api', 'context', 'linearity', 'maxduration', 'mimes', 'protocols']; +var mapVideo = (slot, videoFromBid) => { + var videoFromSlot = deepAccess(slot, 'mediaTypes.video'); + var videoParamsUsed = ['api', 'context', 'linearity', 'maxduration', 'mimes', 'protocols', 'playbackmethod']; var videoAssets; - if (video) { + if (videoFromSlot) { + const video = videoFromBid ? Object.assign(videoFromSlot, videoFromBid) : videoFromSlot; var videoParams = Object.keys(video); var playerSize = video.playerSize; videoAssets = {}; // player width / height @@ -292,7 +332,7 @@ var mapVideo = slot => { const mapImpression = slot => { const { adUnitCode, bidId, params = {}, ortb2Imp = {} } = slot; - const { id, siteId } = params; + const { id, siteId, video } = params; const { ext = {} } = ortb2Imp; /* @@ -313,12 +353,13 @@ const mapImpression = slot => { id: id && siteId ? id.padStart(3, '0') : 'bidid-' + bidId, banner: mapBanner(slot), native: mapNative(slot), - video: mapVideo(slot), + video: mapVideo(slot, video), tagid: adUnitCode, ext, }; // Check floorprices for this imp + const currency = getCurrency(); if (typeof slot.getFloor === 'function') { var bannerFloor = 0; var nativeFloor = 0; @@ -328,20 +369,24 @@ const mapImpression = slot => { bannerFloor = slot.sizes.reduce(function (prev, next) { var currentFloor = slot.getFloor({ mediaType: 'banner', - size: next + size: next, + currency }).floor; return prev > currentFloor ? prev : currentFloor; }, 0); } nativeFloor = slot.getFloor({ - mediaType: 'native' + mediaType: 'native', currency }); videoFloor = slot.getFloor({ - mediaType: 'video' + mediaType: 'video', currency }); imp.bidfloor = Math.max(bannerFloor, nativeFloor, videoFloor); + } else { + imp.bidfloor = 0; } + imp.bidfloorcur = currency; return imp; } @@ -463,13 +508,19 @@ const renderCreative = (site, auctionId, bid, seat, request) => { window.ref = "${site.ref}"; window.adlabel = "${site.adLabel ? site.adLabel : ''}"; window.pubid = "${site.publisherId ? site.publisherId : ''}"; + window.requestPVID = "${pageView.id}"; `; + if (gam) { + adcode += `window.gam = ${JSON.stringify(gam)};`; + } + adcode += `
- + + `; @@ -492,18 +543,12 @@ const spec = { 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, @@ -512,12 +557,15 @@ const spec = { publisher: publisherId ? { id: publisherId } : undefined, page, domain, - ref + ref, + content: { language: getContentLanguage() }, }, imp: validBidRequests.map(slot => mapImpression(slot)), + cur: [getCurrency()], tmax, user: {}, regs: {}, + device: { language: getBrowserLanguage() }, test: testMode, }; @@ -539,6 +587,7 @@ const spec = { const bids = []; const site = JSON.parse(request.data).site; // get page and referer data from request site.sn = response.sn || 'mc_adapter'; // WPM site name (wp_sn) + pageView.sn = site.sn; // store site_name (for syncing and notifications) let seat; if (response.seatbid !== undefined) { @@ -547,6 +596,7 @@ const spec = { 'bidid-' prefix indicates oneCode (parameterless) request and response */ response.seatbid.forEach(seatbid => { + let creativeCache; seat = seatbid.seat; seatbid.bid.forEach(serverBid => { // get data from bid response @@ -572,11 +622,12 @@ const spec = { ext also might contain publisherId and custom ad label */ - const { siteid, slotid, pubid, adlabel } = ext; + const { siteid, slotid, pubid, adlabel, cache } = ext; site.id = siteid || site.id; site.slot = slotid || site.slot; site.publisherId = pubid; site.adLabel = adlabel; + creativeCache = cache; } if (bidRequest && site.id && !strIncludes(site.id, 'bidid')) { @@ -597,6 +648,7 @@ const spec = { meta: { advertiserDomains: adomain, networkName: seat, + pricepl: ext && ext.pricepl, }, netRevenue: true, }; @@ -608,6 +660,7 @@ const spec = { bid.mediaType = 'video'; bid.vastXml = serverBid.adm; bid.vastContent = serverBid.adm; + bid.vastUrl = creativeCache; } else if (isNativeAd(serverBid)) { // native bid.mediaType = 'native'; @@ -659,10 +712,11 @@ const spec = { }, getUserSyncs(syncOptions, serverResponses, gdprConsent) { let mySyncs = []; + // TODO: the check on CMP api version does not seem to make sense here. It means "always run the usersync unless an old (v1) CMP was detected". No attention is paid to the consent choices. if (syncOptions.iframeEnabled && consentApiVersion != 1) { mySyncs.push({ type: 'iframe', - url: `${SYNC_URL}?tcf=${consentApiVersion}`, + url: `${SYNC_URL}?tcf=${consentApiVersion}&pvid=${pageView.id}&sn=${pageView.sn}`, }); }; return mySyncs; diff --git a/modules/sspBCBidAdapter.md b/modules/sspBCBidAdapter.md index 0da84857cbf..4ae2e425865 100644 --- a/modules/sspBCBidAdapter.md +++ b/modules/sspBCBidAdapter.md @@ -21,6 +21,7 @@ Optional parameters: - page - tmax - test +- video # Test Parameters ``` diff --git a/modules/staqAnalyticsAdapter.js b/modules/staqAnalyticsAdapter.js index 55d9da54656..b9bfe5212c6 100644 --- a/modules/staqAnalyticsAdapter.js +++ b/modules/staqAnalyticsAdapter.js @@ -24,9 +24,10 @@ const STAQ_EVENTS = { } function buildRequestTemplate(connId) { - const url = staqAdapterRefWin.referer; - const ref = staqAdapterRefWin.referer; - const topLocation = staqAdapterRefWin.referer; + // TODO: what should these pick from refererInfo? + const url = staqAdapterRefWin.topmostLocation; + const ref = staqAdapterRefWin.topmostLocation; + const topLocation = staqAdapterRefWin.topmostLocation; return { ver: ANALYTICS_VERSION, diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js index 96632684bb1..614dbdf6ef6 100644 --- a/modules/stroeerCoreBidAdapter.js +++ b/modules/stroeerCoreBidAdapter.js @@ -17,7 +17,7 @@ function getTopWindowReferrer() { try { return getWindowTop().document.referrer; } catch (e) { - return getWindowSelf().referrer; + return ''; } } @@ -119,13 +119,16 @@ export const spec = { buildRequests: function (validBidRequests = [], bidderRequest) { const anyBid = bidderRequest.bids[0]; + const refererInfo = bidderRequest.refererInfo; + const payload = { id: bidderRequest.auctionId, bids: [], ref: getTopWindowReferrer(), ssl: isSecureWindow(), mpa: isMainPageAccessible(), - timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart) + timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart), + url: refererInfo && (refererInfo.canonicalUrl || refererInfo.referer) }; const userIds = anyBid.userId; diff --git a/modules/stvBidAdapter.md b/modules/stvBidAdapter.md deleted file mode 100644 index 79e958c3bba..00000000000 --- a/modules/stvBidAdapter.md +++ /dev/null @@ -1,43 +0,0 @@ -# Overview - -``` -Module Name: STV Video Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid@dspx.tv -``` - -# Description - -STV video adapter for Prebid.js 1.x - -# Parameters -``` - var adUnits = [ - { - // video settings - code: 'video-obj', - mediaTypes: { - video: { - context: 'instream', - playerSize: [640, 480] - } - }, - bids: [ - { - bidder: "stv", - params: { - placement: "", // placement ID of inventory with STV - noskip: 1, // 0 or 1 - pfilter: {/* - min_duration: 10, // min duration - max_duration: 30, // max duration - min_bitrate: 300, // min bitrate - max_bitrate: 1600, // max bitrate - */} - } - } - ] - } - ]; -``` - diff --git a/modules/sublimeBidAdapter.js b/modules/sublimeBidAdapter.js index 4dfdd4f3faa..2d177fbe6eb 100644 --- a/modules/sublimeBidAdapter.js +++ b/modules/sublimeBidAdapter.js @@ -154,7 +154,8 @@ function buildRequests(validBidRequests, bidderRequest) { // RefererInfo if (bidderRequest && bidderRequest.refererInfo) { - commonPayload.referer = bidderRequest.refererInfo.referer; + // TODO: is 'topmostLocation' the right value here? + commonPayload.referer = bidderRequest.refererInfo.topmostLocation; commonPayload.numIframes = bidderRequest.refererInfo.numIframes; } // GDPR handling diff --git a/modules/supply2BidAdapter.md b/modules/supply2BidAdapter.md deleted file mode 100644 index 3d86f065abf..00000000000 --- a/modules/supply2BidAdapter.md +++ /dev/null @@ -1,40 +0,0 @@ -# Overview - -Module Name: Supply2 Bidder Adapter -Module Type: Bidder Adapter -Maintainer: vishal@mediadonuts.com - -# Description - -Module that connects to Media Donuts demand source to fetch bids. - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: "supply2", - params: { - uid: '23', - priceType: 'gross' // by default is 'net' - } - } - ] - },{ - code: 'test-div', - sizes: [[728, 90]], - bids: [ - { - bidder: "supply2", - params: { - uid: 24, - priceType: 'gross' - } - } - ] - } - ]; -``` \ No newline at end of file diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index 4cc648a2e04..a48c1aaf55b 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -1,6 +1,6 @@ 'use strict'; -import {deepSetValue, getAdUnitSizes, isFn, isPlainObject, logWarn} from '../src/utils.js'; +import {deepAccess, deepSetValue, getAdUnitSizes, isFn, isPlainObject, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; @@ -38,9 +38,10 @@ export const spec = { const openRtbBidRequest = { id: bidderRequest.auctionId, site: { - domain: config.getConfig('publisherDomain') || location.hostname, - page: refererInfo.referer, - ref: document.referrer + // TODO: does the fallback make sense here? + domain: refererInfo.domain || location.hostname, + page: refererInfo.page, + ref: refererInfo.ref }, device: { ua: navigator.userAgent @@ -48,6 +49,13 @@ export const spec = { imp: [] }; + const callbackTimeout = bidderRequest.timeout; + const globalTimeout = config.getConfig('bidderTimeout'); + const tmax = globalTimeout ? Math.min(globalTimeout, callbackTimeout) : callbackTimeout; + if (tmax) { + openRtbBidRequest.tmax = tmax; + } + const schain = validBidReqs[0].schain; if (schain) { openRtbBidRequest.source = { ext: { schain } }; @@ -79,7 +87,16 @@ export const spec = { imps = this.buildVideoImpressions(adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey); } if (imps.length > 0) { - imps.forEach(i => openRtbBidRequest.imp.push(i)); + imps.forEach(i => { + // Deeply add ext section to all imp[] for GPID, prebid slot id, and anything else down the line + const extSection = deepAccess(bid, 'ortb2Imp.ext'); + if (extSection) { + deepSetValue(i, 'ext', extSection); + } + + // Add imp[] to request object + openRtbBidRequest.imp.push(i); + }); } }); diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js new file mode 100644 index 00000000000..1a35f9e548e --- /dev/null +++ b/modules/taboolaBidAdapter.js @@ -0,0 +1,249 @@ +'use strict'; + +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {getWindowSelf} from '../src/utils.js' +import {getStorageManager} from '../src/storageManager.js'; + +const BIDDER_CODE = 'taboola'; +const GVLID = 42; +const CURRENCY = 'USD'; +export const END_POINT_URL = 'https://hb.bidder.taboola.com/TaboolaHBOpenRTBRequestHandlerServlet'; +const USER_ID = 'user-id'; +const STORAGE_KEY = `taboola global:${USER_ID}`; +const COOKIE_KEY = 'trc_cookie_storage'; + +/** + * extract User Id by that order: + * 1. local storage + * 2. first party cookie + * 3. rendered trc + * 4. new user set it to 0 + */ +export const userData = { + storageManager: getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}), + getUserId: () => { + const {getFromLocalStorage, getFromCookie, getFromTRC} = userData; + + try { + return getFromLocalStorage() || getFromCookie() || getFromTRC(); + } catch (ex) { + return 0; + } + }, + getFromCookie() { + const {cookiesAreEnabled, getCookie} = userData.storageManager; + if (cookiesAreEnabled()) { + const cookieData = getCookie(COOKIE_KEY); + const userId = userData.getCookieDataByKey(cookieData, USER_ID); + if (userId) { + return userId; + } + } + }, + getCookieDataByKey(cookieData, key) { + const [, value = ''] = cookieData.split(`${key}=`) + return value; + }, + getFromLocalStorage() { + const {hasLocalStorage, localStorageIsEnabled, getDataFromLocalStorage} = userData.storageManager; + + if (hasLocalStorage() && localStorageIsEnabled()) { + return getDataFromLocalStorage(STORAGE_KEY); + } + }, + getFromTRC() { + return window.TRC ? window.TRC.user_id : 0; + } +} + +export const internal = { + getPageUrl: (refererInfo = {}) => { + return refererInfo.page || getWindowSelf().location.href; + }, + getReferrer: (refererInfo = {}) => { + if (refererInfo.ref) { + return refererInfo.ref; + } else { + return getWindowSelf().document.referrer; + } + } +} + +export const spec = { + supportedMediaTypes: [BANNER], + gvlid: GVLID, + code: BIDDER_CODE, + isBidRequestValid: (bidRequest) => { + return !!(bidRequest.sizes && + bidRequest.params && + bidRequest.params.publisherId && + bidRequest.params.tagId); + }, + buildRequests: (validBidRequests, bidderRequest) => { + const [bidRequest] = validBidRequests; + const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; + const {publisherId} = bidRequest.params; + const site = getSiteProperties(bidRequest.params, refererInfo); + const device = {ua: navigator.userAgent}; + const imps = getImps(validBidRequests); + const user = { + buyeruid: userData.getUserId(gdprConsent, uspConsent), + ext: {} + }; + const regs = { + coppa: 0, + ext: {} + }; + + if (gdprConsent.gdprApplies) { + user.ext.consent = bidderRequest.gdprConsent.consentString; + regs.ext.gdpr = 1; + } + + if (uspConsent) { + regs.ext.us_privacy = uspConsent; + } + + if (config.getConfig('coppa')) { + regs.coppa = 1 + } + + const ortb2 = bidderRequest.ortb2 || { + badv: [], + bcat: [] + }; + + const request = { + id: bidderRequest.auctionId, + imp: imps, + site, + device, + source: {fd: 1}, + tmax: bidderRequest.timeout, + bcat: ortb2.bcat, + badv: ortb2.badv, + user, + regs + }; + + const url = [END_POINT_URL, publisherId].join('/'); + + return { + url, + method: 'POST', + data: JSON.stringify(request), + bids: validBidRequests, + options: { + withCredentials: false + }, + }; + }, + interpretResponse: (serverResponse, {bids}) => { + if (!bids) { + return []; + } + + const {bidResponses, cur: currency} = getBidResponses(serverResponse); + + if (!bidResponses) { + return []; + } + + return bids.map((bid, id) => getBid(bid.bidId, currency, bidResponses[id])).filter(Boolean); + }, +}; + +function getSiteProperties({publisherId, bcat = []}, refererInfo) { + const {getPageUrl, getReferrer} = internal; + return { + id: publisherId, + name: publisherId, + domain: refererInfo?.domain || window.location.host, + page: getPageUrl(refererInfo), + ref: getReferrer(refererInfo), + publisher: { + id: publisherId + }, + content: { + language: navigator.language + } + } +} + +function getImps(validBidRequests) { + return validBidRequests.map((bid, id) => { + const {tagId, bidfloor = null, bidfloorcur = CURRENCY} = bid.params; + + return { + id: id + 1, + banner: getBanners(bid), + tagid: tagId, + bidfloor, + bidfloorcur, + }; + }); +} + +function getBanners(bid) { + return getSizes(bid.sizes); +} + +function getSizes(sizes) { + return { + format: sizes.map(size => { + return { + w: size[0], + h: size[1] + } + }) + } +} + +function getBidResponses({body}) { + if (!body) { + return []; + } + + const {seatbid, cur} = body; + + if (!seatbid.length || !seatbid[0].bid) { + return []; + } + + return { + bidResponses: seatbid[0].bid, + cur + }; +} + +function getBid(requestId, currency, bidResponse) { + if (!bidResponse) { + return; + } + + const { + price: cpm, crid: creativeId, adm: ad, w: width, h: height, adomain: advertiserDomains, meta = {} + } = bidResponse; + + if (advertiserDomains && advertiserDomains.length > 0) { + meta.advertiserDomains = advertiserDomains + } + + return { + requestId, + ttl: 60, + mediaType: BANNER, + cpm, + creativeId, + currency, + ad, + width, + height, + meta, + netRevenue: false + }; +} + +registerBidder(spec); diff --git a/modules/taboolaBidAdapter.md b/modules/taboolaBidAdapter.md new file mode 100644 index 00000000000..33e72592796 --- /dev/null +++ b/modules/taboolaBidAdapter.md @@ -0,0 +1,49 @@ +# Overview + +``` +Module Name: Taboola Adapter +Module Type: Bidder Adapter +Maintainer: prebid@taboola.com +``` + +# Description + +Module that connects to Taboola bidder to fetch bids. +- Supports `display` format +- Uses `OpenRTB` standard + +The Taboola Bidding adapter requires setup before beginning. Please contact us on prebid@taboola.com + +# Test Display Parameters +``` javascript + var adUnits = [{ + code: 'your-unit-container-id', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'taboola', + params: { + tagId: 'tester-placement', // Placement Name + publisherId: 'tester-pub', // your-publisher-id + bidfloor: 0.25, // Optional - default is null + bcat: ['IAB1-1'], // Optional - default is [] + badv: ['example.com'] // Optional - default is [] + } + }] +}]; +``` + +# Parameters + +| Name | Scope | Description | Example | Type | +|----------------|----------|---------------------------------------------------------|----------------------------|--------------| +| `tagId` | required | Tag ID / Placement Name
| `'Below The Article'` | `String` | +| `publisherId` | required | Numeric Publisher ID
(as provided by Taboola) | `'1234567'` | `String` | +| `bcat` | optional | List of blocked advertiser categories (IAB) | `['IAB1-1']` | `Array` | +| `badv` | optional | Blocked Advertiser Domains | `'example.com'` | `String Url` | +| `bidfloor` | optional | CPM bid floor | `0.25` | `Float` | + + diff --git a/modules/talkadsBidAdapter.js b/modules/talkadsBidAdapter.js index 068dce23b43..dae452b9a7d 100644 --- a/modules/talkadsBidAdapter.js +++ b/modules/talkadsBidAdapter.js @@ -5,11 +5,12 @@ import {ajax} from '../src/ajax.js'; const CURRENCY = 'EUR'; const BIDDER_CODE = 'talkads'; +const GVLID = 1074; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [ NATIVE, BANNER ], - params: null, /** * Determines whether or not the given bid request is valid. @@ -31,7 +32,7 @@ export const spec = { utils.logError('VALIDATION FAILED : the parameter "bidder_url" must be defined'); return false; } - this.params = poBid.params; + return !!(poBid.nativeParams || poBid.sizes); }, // isBidRequestValid @@ -54,6 +55,7 @@ export const spec = { } return loOne; }); + let laParams = paValidBidRequests[0].params; const loServerRequest = { cur: CURRENCY, timeout: poBidderRequest.timeout, @@ -71,7 +73,7 @@ export const spec = { loServerRequest.gdpr.consent = poBidderRequest.gdprConsent.consentString; } } - const lsUrl = this.params.bidder_url + '/' + this.params.tag_id; + const lsUrl = laParams.bidder_url + '/' + laParams.tag_id; return { method: 'POST', url: lsUrl, @@ -86,7 +88,7 @@ export const spec = { * @param poPidRequest Request original server request * @return An array of bids which were nested inside the server. */ - interpretResponse: (poServerResponse, poPidRequest) => { + interpretResponse: function (poServerResponse, poPidRequest) { utils.logInfo('interpretResponse : ', poServerResponse); if (!poServerResponse.body) { return []; @@ -120,8 +122,9 @@ export const spec = { */ onBidWon: function (poBid) { utils.logInfo('onBidWon : ', poBid); + let laParams = poBid.params[0]; if (poBid.pbid) { - ajax(this.params.bidder_url + 'won/' + poBid.pbid); + ajax(laParams.bidder_url + 'won/' + poBid.pbid); } }, // onBidWon }; diff --git a/modules/taphypeBidAdapter.md b/modules/taphypeBidAdapter.md deleted file mode 100644 index c6ff40a42ba..00000000000 --- a/modules/taphypeBidAdapter.md +++ /dev/null @@ -1,32 +0,0 @@ -# Overview - -Module Name: TapHype Bidder Adapter -Module Type: Bidder Adapter -Maintainer: admin@taphype.com - -# Description - -You can use this adapter to get a bid from taphype.com. - - -# Test Parameters -```javascript - var adUnits = [ - { - code: 'div-taphype-example', - sizes: [[300, 250]], - bids: [ - { - bidder: "taphype", - params: { - placementId: 12345 - } - } - ] - } - ]; -``` - -Where: - -* placementId - TapHype Placement ID diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 769e8f73565..fd038f3104e 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -5,6 +5,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; +import {parseDomain} from '../src/refererDetection.js'; const BIDDER_CODE = 'tappx'; const GVLID_CODE = 628; @@ -555,19 +556,9 @@ export function _checkParamDataType(key, value, datatype) { } export function _extractPageUrl(validBidRequests, bidderRequest) { - let referrer = deepAccess(bidderRequest, 'refererInfo.referer'); - let page = deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || deepAccess(window, 'location.href'); - let paramUrl = deepAccess(validBidRequests, 'params.domainUrl') || config.getConfig('pageUrl'); - - let domainUrl = referrer || page || paramUrl; - - try { - domainUrl = domainUrl.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n?]+)/img)[0].replace(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?/img, ''); - } catch (error) { - domainUrl = undefined; - } - - return domainUrl; + // TODO: does the fallback make sense? + let url = bidderRequest?.refererInfo?.page || bidderRequest.refererInfo?.topmostLocation; + return parseDomain(url, {noLeadingWww: true}); } registerBidder(spec); diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js index 4deb63b6426..f8cb58218cd 100644 --- a/modules/targetVideoBidAdapter.js +++ b/modules/targetVideoBidAdapter.js @@ -42,6 +42,24 @@ export const spec = { }, schain: schain }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + + if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { + let ac = bidderRequest.gdprConsent.addtlConsent; + let acStr = ac.substring(ac.indexOf('~') + 1); + payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); + } + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent + } + return formatRequest(payload, bidderRequest); }, diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index a8902c896f6..c40f76e743e 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -54,7 +54,6 @@ export const spec = { data: bids, deviceWidth: screen.width, hb_version: '$prebid.version$', - ...getFLoCParameters(deepAccess(validBidRequests, '0.userId.flocId')), ...getUnifiedId2Parameter(deepAccess(validBidRequests, '0.userId.uid2')), ...getFirstPartyTeadsIdParameter() }; @@ -68,7 +67,7 @@ export const spec = { let isCmp = typeof gdpr.gdprApplies === 'boolean'; let isConsentString = typeof gdpr.consentString === 'string'; let status = isCmp - ? findGdprStatus(gdpr.gdprApplies, gdpr.vendorData, gdpr.apiVersion) + ? findGdprStatus(gdpr.gdprApplies, gdpr.vendorData) : gdprStatus.CMP_NOT_FOUND_OR_ERROR; payload.gdpr_iab = { consent: isConsentString ? gdpr.consentString : '', @@ -127,8 +126,8 @@ export const spec = { function getReferrerInfo(bidderRequest) { let ref = ''; - if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - ref = bidderRequest.refererInfo.referer; + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + ref = bidderRequest.refererInfo.page; } return ref; } @@ -166,10 +165,10 @@ function getTimeToFirstByte(win) { return ttfbWithTimingV1 ? ttfbWithTimingV1.toString() : ''; } -function findGdprStatus(gdprApplies, gdprData, apiVersion) { +function findGdprStatus(gdprApplies, gdprData) { let status = gdprStatus.GDPR_APPLIES_PUBLISHER; if (gdprApplies) { - if (isGlobalConsent(gdprData, apiVersion)) { + if (gdprData && !gdprData.isServiceSpecific) { status = gdprStatus.GDPR_APPLIES_GLOBAL; } } else { @@ -178,14 +177,6 @@ function findGdprStatus(gdprApplies, gdprData, apiVersion) { return status; } -function isGlobalConsent(gdprData, apiVersion) { - return gdprData && apiVersion === 1 - ? (gdprData.hasGlobalScope || gdprData.hasGlobalConsent) - : gdprData && apiVersion === 2 - ? !gdprData.isServiceSpecific - : false; -} - function buildRequestObject(bid) { const reqObj = {}; let placementId = getValue(bid.params, 'placementId'); @@ -237,20 +228,6 @@ function _validateId(id) { return (parseInt(id) > 0); } -/** - * Get FLoC parameters to be sent in the bid request. - * @param `{id: string, version: string} | undefined` optionalFlocId FLoC user ID object available if "flocIdSystem" module is enabled. - * @returns `{} | {cohortId: string} | {cohortVersion: string} | {cohortId: string, cohortVersion: string}` - */ -function getFLoCParameters(optionalFlocId) { - if (!optionalFlocId) { - return {}; - } - const cohortId = optionalFlocId.id ? { cohortId: optionalFlocId.id } : {}; - const cohortVersion = optionalFlocId.version ? { cohortVersion: optionalFlocId.version } : {}; - return { ...cohortId, ...cohortVersion }; -} - /** * Get unified ID v2 parameter to be sent in bid request. * @param `{id: string} | undefined` optionalUid2 uid2 user ID object available if "uid2IdSystem" module is enabled. diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js index 42913414cbc..5fb71d1d627 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -9,10 +9,10 @@ const EVENTS_ENDPOINT = `events.${DOMAIN}/diag`; export const spec = { code: BIDDER_CODE, - gvlid: 52, + gvlid: 202, aliases: [ - { code: 'tremor', gvlid: 52 }, - { code: 'tremorvideo', gvlid: 52 } + { code: 'tremor', gvlid: 202 }, + { code: 'tremorvideo', gvlid: 202 } ], supportedMediaTypes: [VIDEO], /** @@ -247,8 +247,9 @@ function generateUrl(bid, bidderRequest) { } } - if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { - url += (`&referrer=${encodeURIComponent(bidderRequest.refererInfo.referer)}`); + if (bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + // TODO: is 'page' the right value here? + url += (`&referrer=${encodeURIComponent(bidderRequest.refererInfo.page)}`); } } diff --git a/modules/theAdxBidAdapter.js b/modules/theAdxBidAdapter.js index d7a79fe74d0..92bfb89f5e0 100644 --- a/modules/theAdxBidAdapter.js +++ b/modules/theAdxBidAdapter.js @@ -155,7 +155,8 @@ export const spec = { withCredentials: true, }, bidder: 'theadx', - referrer: encodeURIComponent(bidderRequest.refererInfo.referer), + // TODO: is 'page' the right value here? + referrer: encodeURIComponent(bidderRequest.refererInfo.page || ''), data: generatePayload(bidRequest, bidderRequest), mediaTypes: bidRequest['mediaTypes'], requestId: bidderRequest.bidderRequestId, @@ -314,7 +315,7 @@ export const spec = { } let buildSiteComponent = (bidRequest, bidderRequest) => { - let loc = parseUrl(bidderRequest.refererInfo.referer, { + let loc = parseUrl(bidderRequest.refererInfo.page || '', { decodeSearchAsString: true }); diff --git a/modules/timBidAdapter.md b/modules/timBidAdapter.md deleted file mode 100644 index 684f2e5f7c4..00000000000 --- a/modules/timBidAdapter.md +++ /dev/null @@ -1,26 +0,0 @@ -# Overview - -``` -Module Name: tim Bidder Adapter -Module Type: Bidder Adapter -Maintainer: boris@thetimmedia.com -``` - -# Description - -Module that connects to tim's demand sources - -# Test Parameters -``` - var adUnits = [{ - "code":"99", - "sizes":[[300,250]], - "bids":[{"bidder":"tim", - "params":{ - "placementCode":"testPlacementCode", - "publisherid":"testpublisherid" - } - }] - }] -``` - diff --git a/modules/tncIdSystem.js b/modules/tncIdSystem.js new file mode 100644 index 00000000000..24e3c79d4df --- /dev/null +++ b/modules/tncIdSystem.js @@ -0,0 +1,63 @@ +import { submodule } from '../src/hook.js'; +import { logInfo } from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; + +const MODULE_NAME = 'tncId'; +let url = null; + +const waitTNCScript = (tncNS) => { + return new Promise((resolve, reject) => { + var tnc = window[tncNS]; + if (!tnc) reject(new Error('No TNC Object')); + if (tnc.tncid) resolve(tnc.tncid); + tnc.ready(() => { + tnc = window[tncNS]; + if (tnc.tncid) resolve(tnc.tncid); + else tnc.on('data-sent', () => resolve(tnc.tncid)); + }); + }); +} + +const loadRemoteScript = () => { + return new Promise((resolve) => { + loadExternalScript(url, MODULE_NAME, resolve); + }) +} + +const tncCallback = function (cb) { + let tncNS = '__tnc'; + let promiseArray = []; + if (!window[tncNS]) { + tncNS = '__tncPbjs'; + promiseArray.push(loadRemoteScript()); + } + + return Promise.all(promiseArray).then(() => waitTNCScript(tncNS)).then(cb).catch(() => cb()); +} + +export const tncidSubModule = { + name: MODULE_NAME, + decode(id) { + return { + tncid: id + }; + }, + gvlid: 750, + getId(config, consentData) { + const gdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; + const consentString = gdpr ? consentData.consentString : ''; + + if (gdpr && !consentString) { + logInfo('Consent string is required for TNCID module'); + return; + } + + if (config.params && config.params.url) { url = config.params.url; } + + return { + callback: function (cb) { return tncCallback(cb); } + } + } +} + +submodule('userId', tncidSubModule) diff --git a/modules/tncIdSystem.md b/modules/tncIdSystem.md new file mode 100644 index 00000000000..f0f98e9098f --- /dev/null +++ b/modules/tncIdSystem.md @@ -0,0 +1,33 @@ +# TNCID UserID Module + +### Prebid Configuration + +First, make sure to add the TNCID submodule to your Prebid.js package with: + +``` +gulp build --modules=tncIdSystem,userId +``` + +### TNCIDIdSystem module Configuration + +You can configure this submodule in your `userSync.userIds[]` configuration: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'tncId', + params: { + url: 'https://js.tncid.app/remote.min.js' //Optional + } + }], + syncDelay: 5000 + } +}); +``` +#### Configuration Params + +| Param Name | Required | Type | Description | +| --- | --- | --- | --- | +| name | Required | String | ID value for the TNCID module: `"tncId"` | +| params.url | Optional | String | Provide TNC fallback script URL, this script is loaded if there is no TNC script on page | diff --git a/modules/topRTBBidAdapter.md b/modules/topRTBBidAdapter.md deleted file mode 100644 index d1930c928e4..00000000000 --- a/modules/topRTBBidAdapter.md +++ /dev/null @@ -1,30 +0,0 @@ -# Overview - -``` -Module Name: topRTB Bidder Adapter -Module Type: Bidder Adapter -Maintainer: karthikeyan.d@djaxtech.com -``` - -# Description - -topRTB Bidder Adapter for Prebid.js. -Only Banner & video format is supported. - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div-0', - sizes: [[728, 90]], // a display size - bids: [ - { - bidder: 'topRTB', - params: { - adUnitId: 'c5c06f77430c4c33814a0577cb4cc978' - } - } - ] - } - ]; -``` diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 006357cd4b9..8687ab06f4c 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -1,13 +1,16 @@ /* eslint-disable no-tabs */ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { parseUrl, deepAccess } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; export const ADAPTER_VERSION = '1'; const SUPPORTED_AD_TYPES = [BANNER]; - const BIDDER_CODE = 'tpmn'; const URL = 'https://ad.tpmn.co.kr/prebidhb.tpmn'; +const IFRAMESYNC = 'https://ad.tpmn.co.kr/sync.tpmn?type=iframe'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, @@ -18,20 +21,20 @@ export const spec = { * @param {object} bid The bid to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { return 'params' in bid && - 'inventoryId' in bid.params && - 'publisherId' in bid.params && - !isNaN(Number(bid.params.inventoryId)) && - bid.params.inventoryId > 0 && - (typeof bid.mediaTypes.banner.sizes != 'undefined'); // only accepting appropriate sizes + 'inventoryId' in bid.params && + 'publisherId' in bid.params && + !isNaN(Number(bid.params.inventoryId)) && + bid.params.inventoryId > 0 && + (typeof bid.mediaTypes.banner.sizes != 'undefined'); // only accepting appropriate sizes }, /** - * @param {BidRequest[]} bidRequests - * @param {*} bidderRequest - * @return {ServerRequest} - */ + * @param {BidRequest[]} bidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ buildRequests: (bidRequests, bidderRequest) => { if (bidRequests.length === 0) { return []; @@ -49,11 +52,11 @@ export const spec = { }]; }, /** - * 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. - */ + * 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, serverRequest) { if (!Array.isArray(serverResponse.body)) { return []; @@ -63,7 +66,48 @@ export const spec = { // our server directly returns the format needed by prebid.js so no more // transformation is needed here. return bidResults; - } + }, + + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncArr = []; + if (syncOptions.iframeEnabled) { + let policyParam = ''; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + policyParam += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + policyParam += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + policyParam += `&ccpa_consent=${uspConsent.consentString}`; + } + const coppa = config.getConfig('coppa') ? 1 : 0; + policyParam += `&coppa=${coppa}`; + syncArr.push({ + type: 'iframe', + url: IFRAMESYNC + policyParam + }) + } else { + syncArr.push({ + type: 'image', + url: 'https://x.bidswitch.net/sync?ssp=tpmn' + }); + syncArr.push({ + type: 'image', + url: 'https://gocm.c.appier.net/tpmn' + }); + syncArr.push({ + type: 'image', + url: 'https://info.mmnneo.com/getGuidRedirect.info?url=https%3A%2F%2Fad.tpmn.co.kr%2Fcookiesync.tpmn%3Ftpmn_nid%3Dbf91e8b3b9d3f1af3fc1d657f090b4fb%26tpmn_buid%3D' + }); + syncArr.push({ + type: 'image', + url: 'https://sync.aralego.com/idSync?redirect=https%3A%2F%2Fad.tpmn.co.kr%2FpixelCt.tpmn%3Ftpmn_nid%3Dde91e8b3b9d3f1af3fc1d657f090b815%26tpmn_buid%3DSspCookieUserId' + }); + } + return syncArr; + }, }; registerBidder(spec); @@ -72,13 +116,13 @@ registerBidder(spec); * Creates site description object */ function createSite(refInfo) { - let url = parseUrl(refInfo.referer); + let url = parseUrl(refInfo.page || ''); let site = { 'domain': url.hostname, 'page': url.protocol + '://' + url.hostname + url.pathname }; - if (self === top && document.referrer) { - site.ref = document.referrer; + if (refInfo.ref) { + site.ref = refInfo.ref } let keywords = document.getElementsByTagName('meta')['keywords']; if (keywords && keywords.content) { diff --git a/modules/trafficgateBidAdapter.js b/modules/trafficgateBidAdapter.js new file mode 100644 index 00000000000..66a2a9a0152 --- /dev/null +++ b/modules/trafficgateBidAdapter.js @@ -0,0 +1,92 @@ +import { getWindowLocation, deepAccess } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'trafficgate'; +const URL = 'https://[HOST].bc-plugin.com/?c=o&m=multi' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: function (bid) { + return !!(bid.bidId && bid.params && parseInt(bid.params.placementId) && bid.params.host) + }, + /** + * 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) { + if (validBidRequests && validBidRequests.length === 0) return []; + + const location = getWindowLocation() + const placements = [] + + validBidRequests.forEach((bidReq) => { + placements.push({ + placementId: bidReq.params.placementId, + bidId: bidReq.bidId, + traffic: getMediatype(bidReq) + }) + }) + + return { + method: 'POST', + url: URL.replace('[HOST]', validBidRequests[0].params.host), + data: { + language: (navigator && navigator.language) ? navigator.language : '', + secure: +(location.protocol === 'https:'), + host: location.hostname, + page: location.pathname, + placements: placements + } + } + }, + + interpretResponse: function (opts) { + const body = opts.body + const response = [] + + for (let i = 0; i < body.length; i++) { + const item = body[i] + if (isBidResponseValid(item)) { + response.push(item) + } + } + + return response + } +} + +registerBidder(spec) + +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) + case NATIVE: + return Boolean(bid.title && bid.image && bid.impressionTrackers) + default: + return false + } +} + +function getMediatype(bidRequest) { + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + return BANNER; + } + if (deepAccess(bidRequest, 'mediaTypes.video')) { + return VIDEO; + } + if (deepAccess(bidRequest, 'mediaTypes.native')) { + return NATIVE; + } +} diff --git a/modules/trafficgateBidAdapter.md b/modules/trafficgateBidAdapter.md new file mode 100644 index 00000000000..de4449c341e --- /dev/null +++ b/modules/trafficgateBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +``` +Module Name: TrafficGate Bidder Adapter +Module Type: Bidder Adapter +Maintainer: publishers@bidscube.com +``` + +# Description + +Module that connects to TrafficGate demand sources + +# Test Parameters +``` + var adUnits = [{ + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'trafficgate', + params: { + placementId: 0, + host: 'example' + } + }] + }]; +``` diff --git a/modules/trafficrootsBidAdapter.md b/modules/trafficrootsBidAdapter.md deleted file mode 100644 index 2aceb0c866b..00000000000 --- a/modules/trafficrootsBidAdapter.md +++ /dev/null @@ -1,37 +0,0 @@ -# Overview - -Module Name: Trafficroots Bid Adapter - -Module Type: Bidder Adapter - -Maintainer: cary@trafficroots.com - -# Description - -Module that connects to Trafficroots demand sources - -# Test Parameters -```javascript - - var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250],[300,600]], // a display size - bids: [ - { - bidder: 'trafficroots', - params: { - zoneId: 'aa0444af31', - deliveryUrl: location.protocol + '//service.trafficroots.com/prebid' - } - },{ - bidder: 'trafficroots', - params: { - zoneId: '8f527a4835', - deliveryUrl: location.protocol + '//service.trafficroots.com/prebid' - } - } - ] - } - ]; -``` diff --git a/modules/trendqubeBidAdapter.md b/modules/trendqubeBidAdapter.md deleted file mode 100644 index 8b72c225575..00000000000 --- a/modules/trendqubeBidAdapter.md +++ /dev/null @@ -1,53 +0,0 @@ -# Overview - -``` -Module Name: trendqube Bidder Adapter -Module Type: trendqube Bidder Adapter -``` - -# Description - -Module that connects to trendqube demand sources - -# Test Parameters -``` - var adUnits = [ - // Will return static test banner - { - code: 'placementId_0', - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - bids: [ - { - bidder: 'trendqube', - 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: 'trendqube', - params: { - placementId: 0, - traffic: 'video' - } - } - ] - } - ]; -``` diff --git a/modules/tribeosBidAdapter.md b/modules/tribeosBidAdapter.md deleted file mode 100644 index 670810abec9..00000000000 --- a/modules/tribeosBidAdapter.md +++ /dev/null @@ -1,31 +0,0 @@ -# Overview - -``` -Module Name: tribeOS Bidder Adapter -Module Type: Bidder Adapter -Maintainer: dev@tribeos.io -``` - -# Description - -tribeOS adapter - -# Test Parameters -``` - var adUnits = [{ - code: 'test-tribeos', - mediaTypes: { - banner: { - sizes: [ - [300, 250] - ], - } - }, - bids: [{ - bidder: "tribeos", - params: { - placementId: '12345' // REQUIRED - } - }] - }]; -``` diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 215769e9812..df4f9a9ba38 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -1,7 +1,8 @@ -import { tryAppendQueryString, logMessage, isEmpty, isStr, isPlainObject, isArray, logWarn } from '../src/utils.js'; +import { tryAppendQueryString, logMessage, logError, isEmpty, isStr, isPlainObject, isArray, logWarn } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; const GVLID = 28; const BIDDER_CODE = 'triplelift'; @@ -10,6 +11,7 @@ const BANNER_TIME_TO_LIVE = 300; const INSTREAM_TIME_TO_LIVE = 3600; let gdprApplies = true; let consentString = null; +export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); export const tripleliftAdapterSpec = { gvlid: GVLID, @@ -21,13 +23,13 @@ export const tripleliftAdapterSpec = { buildRequests: function(bidRequests, bidderRequest) { let tlCall = STR_ENDPOINT; - let data = _buildPostBody(bidRequests); + let data = _buildPostBody(bidRequests, bidderRequest); tlCall = tryAppendQueryString(tlCall, 'lib', 'prebid'); tlCall = tryAppendQueryString(tlCall, 'v', '$prebid.version$'); if (bidderRequest && bidderRequest.refererInfo) { - let referrer = bidderRequest.refererInfo.referer; + let referrer = bidderRequest.refererInfo.page; tlCall = tryAppendQueryString(tlCall, 'referrer', referrer); } @@ -107,10 +109,10 @@ function _getSyncType(syncOptions) { if (syncOptions.pixelEnabled) return 'image'; } -function _buildPostBody(bidRequests) { +function _buildPostBody(bidRequests, bidderRequest) { let data = {}; let { schain } = bidRequests[0]; - const globalFpd = _getGlobalFpd(); + const globalFpd = _getGlobalFpd(bidderRequest); data.imp = bidRequests.map(function(bidRequest, index) { let imp = { @@ -175,28 +177,45 @@ function _getORTBVideo(bidRequest) { function _getFloor (bid) { let floor = null; if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: _isInstreamBidRequest(bid) ? 'video' : 'banner', - size: '*' - }); - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { - floor = parseFloat(floorInfo.floor); + try { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: _isInstreamBidRequest(bid) ? 'video' : 'banner', + size: '*' + }); + if (typeof floorInfo === 'object' && + floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { + floor = parseFloat(floorInfo.floor); + } + } catch (err) { + logError('Triplelift: getFloor threw an error: ', err); } } return floor !== null ? floor : bid.params.floor; } -function _getGlobalFpd() { +function _getGlobalFpd(bidderRequest) { const fpd = {}; const context = {} const user = {}; - const ortbData = config.getLegacyFpd(config.getConfig('ortb2')) || {}; + const ortbData = bidderRequest.ortb2 || {}; + const opeCloudStorage = _fetchOpeCloud(); - const fpdContext = Object.assign({}, ortbData.context); + const fpdContext = Object.assign({}, ortbData.site); const fpdUser = Object.assign({}, ortbData.user); + if (opeCloudStorage) { + fpdUser.data = fpdUser.data || [] + try { + fpdUser.data.push({ + name: 'www.1plusx.com', + ext: opeCloudStorage + }) + } catch (err) { + logError('Triplelift: error adding 1plusX segments: ', err); + } + } + _addEntries(context, fpdContext); _addEntries(user, fpdUser); @@ -209,6 +228,18 @@ function _getGlobalFpd() { return fpd; } +function _fetchOpeCloud() { + const opeCloud = storage.getDataFromLocalStorage('opecloud_ctx'); + if (!opeCloud) return null; + try { + const parsedJson = JSON.parse(opeCloud); + return parsedJson + } catch (err) { + logError('Triplelift: error parsing JSON: ', err); + return null + } +} + function _getAdUnitFpd(adUnitFpd) { const fpd = {}; const context = {}; diff --git a/modules/trustpidSystem.js b/modules/trustpidSystem.js index 051775fa777..27ca4bf6340 100644 --- a/modules/trustpidSystem.js +++ b/modules/trustpidSystem.js @@ -4,7 +4,7 @@ * @module modules/trustpidSystem * @requires module:modules/userId */ -import { logInfo, logError } from '../src/utils.js'; +import { logInfo } from '../src/utils.js'; import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -22,10 +22,9 @@ export const storage = getStorageManager({gvlid: null, moduleName: MODULE_NAME}) * @param event */ function messageHandler(event) { - let msg; try { - if (event && event.data && typeof event.data === 'string' && event.data) { - msg = JSON.parse(event.data); + if (event && event.data && typeof event.data === 'string') { + const msg = JSON.parse(event.data); if (msg.msgType === 'MNOSELECTOR' && msg.body && msg.body.url) { let URL = msg.body.url.split('//'); let domainURL = URL[1].split('/'); @@ -35,7 +34,7 @@ function messageHandler(event) { } } } catch (e) { - logError(e); + logInfo(`${LOG_PREFIX}: Unsupported message caught. Origin: ${event.origin}, data: ${event.data}.`); } } @@ -44,31 +43,13 @@ function messageHandler(event) { * @param domain */ function getDomainAcronym(domain) { - let acronym = ''; const prefix = '-'; - switch (domain) { - case 'tmi.mno.link': - acronym = 'ndye'; - break; - case 'tmi.vodafone.de': - acronym = 'pqnx'; - break; - case 'tmi.telekom.de': - acronym = 'avgw'; - break; - case 'tmi.tmid.es': - acronym = 'kjws'; - break; - case 'uat.mno.link': - acronym = 'xxxx'; - break; - case 'es.tmiservice.orange.com': - acronym = 'aplw'; - break; - default: - return 'none'; + const acronym = window.FC_CONF?.TELCO_ACRONYM?.[domain]; + if (!acronym) { + logInfo(`${LOG_PREFIX}: No acronym found for domain: ${domain}`); + return; } - return mnoAcronym = prefix + acronym; + mnoAcronym = prefix + acronym; } // Set a listener to handle the iframe response message. diff --git a/modules/trustpidSystem.md b/modules/trustpidSystem.md index c4309c9d807..c1ad1ab567b 100644 --- a/modules/trustpidSystem.md +++ b/modules/trustpidSystem.md @@ -5,41 +5,18 @@ trustpid User Id Module. First, make sure to add the trustpid submodule to your Prebid.js package with: ``` -gulp build --modules=userId,adfBidAdapter,trustpidSystem -``` - -The following configuration parameters are available: - -``` -pbjs.setConfig({ - userSync: { - userIds: [ - { - name: 'trustpid', - params: { - maxDelayTime: 1000, - }, - bidders: ["adf"], - storage: { - type: "html5", - name: "trustpid", - expires: 1, //days - }, - } - ], - } -}); +gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,trustpidSystem ``` ## Parameter Descriptions -| Param under userSync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | The name of the module | `"trustpid"` -| params | Required | Object | Object with configuration parameters for trustpid User Id submodule | - | -| params.maxDelayTime | Required | Integer | Max amount of time (in seconds) before looking into storage for data | 2500 | -| bidders | Required | Array of Strings | An array of bidder codes to which this user ID may be sent. Currently required and supporting AdformOpenRTB | `["adf"]` | -| storage | Required | Object | Local storage configuration object | - | -| storage.type | Required | String | Type of the storage that would be used to store user ID. Must be `"html5"` to utilise HTML5 local storage. | `"html5"` | -| storage.name | Required | String | The name of the key in local storage where the user ID will be stored. | `"trustpid"` | -| storage.expires | Required | Integer | How long (in days) the user ID information will be stored. For safety reasons, this information is required.| `1` | \ No newline at end of file +| Params under userSync.userIds[] | Type | Description | Example | +| --- | --- | --- | --- | +| name | String | The name of the module | `"trustpid"` | +| params | Object | Object with configuration parameters for trustpid User Id submodule | - | +| params.maxDelayTime | Integer | Max amount of time (in seconds) before looking into storage for data | 2500 | +| bidders | Array of Strings | An array of bidder codes to which this user ID may be sent. Currently required and supporting AdformOpenRTB | [`"adf"`, `"adformPBS"`, `"ix"`] | +| storage | Object | Local storage configuration object | - | +| storage.type | String | Type of the storage that would be used to store user ID. Must be `"html5"` to utilise HTML5 local storage. | `"html5"` | +| storage.name | String | The name of the key in local storage where the user ID will be stored. | `"trustpid"` | +| storage.expires | Integer | How long (in days) the user ID information will be stored. For safety reasons, this information is required.| `1` | \ No newline at end of file diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js deleted file mode 100644 index 7d40a0b0452..00000000000 --- a/modules/trustxBidAdapter.js +++ /dev/null @@ -1,532 +0,0 @@ -import {isEmpty, deepAccess, logError, logWarn, parseGPTSingleSizeArrayToRtbSize, mergeDeep} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { Renderer } from '../src/Renderer.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; - -const BIDDER_CODE = 'trustx'; -const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; -const TIME_TO_LIVE = 360; -const ADAPTER_SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; -const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; - -const LOG_ERROR_MESS = { - noAuid: 'Bid from response has no auid parameter - ', - noAdm: 'Bid from response has no adm parameter - ', - noBid: 'Array of bid objects is empty', - noPlacementCode: 'Can\'t find in requested bids the bid with auid - ', - emptyUids: 'Uids should be not empty', - emptySeatbid: 'Seatbid array from response has empty item', - emptyResponse: 'Response is empty', - hasEmptySeatbidArray: 'Response has empty seatbid array', - hasNoArrayOfBids: 'Seatbid from response has no array of bid objects - ' -}; - -export const spec = { - code: BIDDER_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.uid; - }, - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests - an array of bids - * @param {bidderRequest} bidderRequest bidder request object - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - if (!validBidRequests.length) { - return null; - } - let pageKeywords = null; - let jwpseg = null; - let permutiveseg = null; - let content = null; - let schain = null; - let userId = null; - let userIdAsEids = null; - let user = null; - let userExt = null; - let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest || {}; - - const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : ''; - const imp = []; - const bidsMap = {}; - - validBidRequests.forEach((bid) => { - if (!bidderRequestId) { - bidderRequestId = bid.bidderRequestId; - } - if (!auctionId) { - auctionId = bid.auctionId; - } - if (!schain) { - schain = bid.schain; - } - if (!userId) { - userId = bid.userId; - } - if (!userIdAsEids) { - userIdAsEids = bid.userIdAsEids; - } - const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp} = bid; - bidsMap[bidId] = bid; - const bidFloor = _getFloor(mediaTypes || {}, bid); - if (rtd) { - const jwTargeting = rtd.jwplayer && rtd.jwplayer.targeting; - if (jwTargeting) { - if (!jwpseg && jwTargeting.segments) { - jwpseg = jwTargeting.segments; - } - if (!content && jwTargeting.content) { - content = jwTargeting.content; - } - } - const permutiveTargeting = rtd.p_standard && rtd.p_standard.targeting; - if (!permutiveseg && permutiveTargeting && permutiveTargeting.segments) { - permutiveseg = permutiveTargeting.segments; - } - } - let impObj = { - id: bidId && bidId.toString(), - tagid: uid.toString(), - ext: { - divid: adUnitCode && adUnitCode.toString() - } - }; - - if (ortb2Imp) { - if (ortb2Imp.instl) { - impObj.instl = ortb2Imp.instl; - } - if (ortb2Imp.ext && ortb2Imp.ext.data) { - impObj.ext.data = ortb2Imp.ext.data; - if (impObj.ext.data.adserver && impObj.ext.data.adserver.adslot) { - impObj.ext.gpid = impObj.ext.data.adserver.adslot.toString(); - } else { - impObj.ext.gpid = ortb2Imp.ext.data.pbadslot && ortb2Imp.ext.data.pbadslot.toString(); - } - } - } - - if (!isEmpty(keywords)) { - if (!pageKeywords) { - pageKeywords = keywords; - } - impObj.ext.bidder = { keywords }; - } - - if (bidFloor) { - impObj.bidfloor = bidFloor; - } - - if (!mediaTypes || mediaTypes[BANNER]) { - const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {}); - if (banner) { - impObj.banner = banner; - } - } - if (mediaTypes && mediaTypes[VIDEO]) { - const video = createVideoRequest(bid, mediaTypes[VIDEO]); - if (video) { - impObj.video = video; - } - } - - if (impObj.banner || impObj.video) { - imp.push(impObj); - } - }); - - const source = { - tid: auctionId && auctionId.toString(), - ext: { - wrapper: 'Prebid_js', - wrapper_version: '$prebid.version$' - } - }; - - if (schain) { - source.ext.schain = schain; - } - - const bidderTimeout = config.getConfig('bidderTimeout') || timeout; - const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; - - let request = { - id: bidderRequestId && bidderRequestId.toString(), - site: { - page: referer - }, - tmax, - source, - imp - }; - - if (content) { - request.site.content = content; - } - - if (jwpseg && jwpseg.length) { - user = { - data: [{ - name: 'iow_labs_pub_data', - segment: segmentProcessing(jwpseg, 'jwpseg'), - }] - }; - } - - const ortb2UserData = config.getConfig('ortb2.user.data'); - if (ortb2UserData && ortb2UserData.length) { - if (!user) { - user = { data: [] }; - } - user = mergeDeep(user, { - data: [...ortb2UserData] - }); - } - - if (gdprConsent && gdprConsent.consentString) { - userExt = {consent: gdprConsent.consentString}; - } - - if (userIdAsEids && userIdAsEids.length) { - userExt = userExt || {}; - userExt.eids = [...userIdAsEids]; - } - - if (userExt && Object.keys(userExt).length) { - user = user || {}; - user.ext = userExt; - } - - if (user) { - request.user = user; - } - - const userKeywords = deepAccess(config.getConfig('ortb2.user'), 'keywords') || null; - const siteKeywords = deepAccess(config.getConfig('ortb2.site'), 'keywords') || null; - - if (userKeywords) { - pageKeywords = pageKeywords || {}; - pageKeywords.user = pageKeywords.user || {}; - pageKeywords.user.ortb2 = [ - { - name: 'keywords', - keywords: userKeywords.split(','), - } - ]; - } - if (siteKeywords) { - pageKeywords = pageKeywords || {}; - pageKeywords.site = pageKeywords.site || {}; - pageKeywords.site.ortb2 = [ - { - name: 'keywords', - keywords: siteKeywords.split(','), - } - ]; - } - - if (pageKeywords) { - pageKeywords = reformatKeywords(pageKeywords); - if (pageKeywords) { - request.ext = { - keywords: pageKeywords - }; - } - } - - if (gdprConsent && gdprConsent.gdprApplies) { - request.regs = { - ext: { - gdpr: gdprConsent.gdprApplies ? 1 : 0 - } - } - } - - if (uspConsent) { - if (!request.regs) { - request.regs = {ext: {}}; - } - request.regs.ext.us_privacy = uspConsent; - } - - return { - method: 'POST', - url: ENDPOINT_URL, - data: JSON.stringify(request), - bidsMap - }; - }, - /** - * Unpack the response from the server into a list of bids. - * - * @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, RendererConst = Renderer) { - serverResponse = serverResponse && serverResponse.body; - const bidResponses = []; - - let errorMessage; - - if (!serverResponse) errorMessage = LOG_ERROR_MESS.emptyResponse; - else if (serverResponse.seatbid && !serverResponse.seatbid.length) { - errorMessage = LOG_ERROR_MESS.hasEmptySeatbidArray; - } - - if (!errorMessage && serverResponse.seatbid) { - serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst); - }); - } - if (errorMessage) logError(errorMessage); - return bidResponses; - }, - getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { - if (syncOptions.pixelEnabled) { - let params = []; - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params.push(`gdpr=${Number(gdprConsent.gdprApplies)}`); - } - if (typeof gdprConsent.consentString === 'string') { - params.push(`gdpr_consent=${gdprConsent.consentString}`); - } - } - if (uspConsent) { - params.push(`us_privacy=${uspConsent}`); - } - const stringParams = params.join('&'); - return { - type: 'image', - url: ADAPTER_SYNC_URL + stringParams - }; - } - } -} - -function _getBidFromResponse(respItem) { - if (!respItem) { - logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!respItem.bid) { - logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); - } else if (!respItem.bid[0]) { - logError(LOG_ERROR_MESS.noBid); - } - return respItem && respItem.bid && respItem.bid[0]; -} - -function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst) { - if (!serverBid) return; - let errorMessage; - if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid); - if (!serverBid.adm && !serverBid.nurl) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid); - else { - const { bidsMap } = bidRequest; - const bid = bidsMap[serverBid.impid]; - - if (!errorMessage && bid) { - const bidResponse = { - requestId: bid.bidId, // bid.bidderRequestId, - cpm: serverBid.price, - width: serverBid.w, - height: serverBid.h, - creativeId: serverBid.auid, // bid.bidId, - currency: 'USD', - netRevenue: false, - ttl: TIME_TO_LIVE, - dealId: serverBid.dealid, - meta: { - advertiserDomains: serverBid.adomain ? serverBid.adomain : [] - }, - }; - if (serverBid.content_type === 'video') { - if (serverBid.adm) { - bidResponse.vastXml = serverBid.adm; - bidResponse.adResponse = { - content: bidResponse.vastXml - }; - } else if (serverBid.nurl) { - bidResponse.vastUrl = serverBid.nurl; - } - bidResponse.mediaType = VIDEO; - if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) { - bidResponse.renderer = createRenderer(bidResponse, { - id: bid.bidId, - url: RENDERER_URL - }, RendererConst); - } - } else { - bidResponse.ad = serverBid.adm; - bidResponse.mediaType = BANNER; - } - - bidResponses.push(bidResponse); - } - } - if (errorMessage) { - logError(errorMessage); - } -} - -function outstreamRender (bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - targetId: bid.adUnitCode, - adResponse: bid.adResponse - }); - }); -} - -function createRenderer (bid, rendererParams, RendererConst) { - const rendererInst = RendererConst.install({ - id: rendererParams.id, - url: rendererParams.url, - loaded: false - }); - - try { - rendererInst.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); - } - - return rendererInst; -} - -function createVideoRequest(bid, mediaType) { - const {playerSize, mimes, durationRangeSec, protocols} = mediaType; - const size = (playerSize || bid.sizes || [])[0]; - if (!size) return; - - let result = parseGPTSingleSizeArrayToRtbSize(size); - - if (mimes) { - result.mimes = mimes; - } - - if (durationRangeSec && durationRangeSec.length === 2) { - result.minduration = durationRangeSec[0]; - result.maxduration = durationRangeSec[1]; - } - - if (protocols && protocols.length) { - result.protocols = protocols; - } - - return result; -} - -function createBannerRequest(bid, mediaType) { - const sizes = mediaType.sizes || bid.sizes; - if (!sizes || !sizes.length) return; - - let format = sizes.map((size) => parseGPTSingleSizeArrayToRtbSize(size)); - let result = parseGPTSingleSizeArrayToRtbSize(sizes[0]); - - if (format.length) { - result.format = format - } - return result; -} - -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); -} - -function reformatKeywords(pageKeywords) { - const formatedPageKeywords = {}; - Object.keys(pageKeywords).forEach((name) => { - const keywords = pageKeywords[name]; - if (keywords) { - if (name === 'site' || name === 'user') { - const formatedKeywords = {}; - Object.keys(keywords).forEach((pubName) => { - if (Array.isArray(keywords[pubName])) { - const formatedPublisher = []; - keywords[pubName].forEach((pubItem) => { - if (typeof pubItem === 'object' && pubItem.name) { - const formatedPubItem = { name: pubItem.name, segments: [] }; - Object.keys(pubItem).forEach((key) => { - if (Array.isArray(pubItem[key])) { - pubItem[key].forEach((keyword) => { - if (keyword) { - if (typeof keyword === 'string') { - formatedPubItem.segments.push({ name: key, value: keyword }); - } else if (key === 'segments' && typeof keyword.name === 'string' && typeof keyword.value === 'string') { - formatedPubItem.segments.push(keyword); - } - } - }); - } - }); - if (formatedPubItem.segments.length) { - formatedPublisher.push(formatedPubItem); - } - } - }); - if (formatedPublisher.length) { - formatedKeywords[pubName] = formatedPublisher; - } - } - }); - formatedPageKeywords[name] = formatedKeywords; - } else { - formatedPageKeywords[name] = keywords; - } - } - }); - return Object.keys(formatedPageKeywords).length && formatedPageKeywords; -} - -/** - * Gets bidfloor - * @param {Object} mediaTypes - * @param {Object} bid - * @returns {Number} floor - */ -function _getFloor (mediaTypes, bid) { - const curMediaType = mediaTypes.video ? 'video' : 'banner'; - let floor = bid.params.bidFloor || 0; - - if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: curMediaType, - size: bid.sizes.map(([w, h]) => ({w, h})) - }); - - if (typeof floorInfo === 'object' && - floorInfo.currency === 'USD' && - !isNaN(parseFloat(floorInfo.floor))) { - floor = Math.max(floor, parseFloat(floorInfo.floor)); - } - } - - return floor; -} - -registerBidder(spec); diff --git a/modules/trustxBidAdapter.md b/modules/trustxBidAdapter.md deleted file mode 100644 index f29d47eaf36..00000000000 --- a/modules/trustxBidAdapter.md +++ /dev/null @@ -1,76 +0,0 @@ -# Overview - -Module Name: TrustX Bidder Adapter -Module Type: Bidder Adapter -Maintainer: paul@trustx.org - -# Description - -Module that connects to TrustX demand source to fetch bids. -TrustX Bid Adapter supports Banner and Video (instream and outstream). - -# Test Parameters -``` - var adUnits = [ - { - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - bids: [ - { - bidder: "trustx", - params: { - uid: '58851', - } - } - ] - },{ - code: 'test-div', - mediaTypes: { - banner: { - sizes: [[728, 90],[300, 250]], - } - }, - bids: [ - { - bidder: "trustx", - params: { - uid: 58851, - keywords: { - site: { - publisher: { - name: 'someKeywordsName', - brandsafety: ['disaster'], - topic: ['stress', 'fear'] - } - } - } - } - } - ] - },{ - code: 'test-div', - mediaTypes: { - video: { - context: 'instream', - playerSize: [640, 360], - mimes: ['video/mp4'], - protocols: [1, 2, 3, 4, 5, 6, 7, 8], - playbackmethod: [2], - skip: 1 - } - }, - bids: [ - { - bidder: "trustx", - params: { - uid: 7697 - } - } - ] - } - ]; -``` diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 4919442336f..55d7490a4ad 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -125,14 +125,14 @@ function getUser(bidderRequest) { function getSite(bidderRequest, firstPartyData) { var site = { id: utils.deepAccess(bidderRequest, 'bids.0.params.siteId'), - page: utils.deepAccess(bidderRequest, 'refererInfo.referer'), + page: utils.deepAccess(bidderRequest, 'refererInfo.page'), publisher: { id: utils.deepAccess(bidderRequest, 'bids.0.params.publisherId'), }, ...firstPartyData.site }; - var publisherDomain = config.getConfig('publisherDomain'); + var publisherDomain = bidderRequest.refererInfo.domain; if (publisherDomain) { utils.deepSetValue(site, 'publisher.domain', publisherDomain); } @@ -373,7 +373,7 @@ export const spec = { * @return {ServerRequest} Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - const firstPartyData = config.getConfig('ortb2') || {}; + const firstPartyData = bidderRequest.ortb2 || {}; let topLevel = { id: bidderRequest.auctionId, imp: validBidRequests.map(bidRequest => getImpression(bidRequest)), diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index ec087d005d6..33bcda29881 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -264,13 +264,13 @@ function getRequestData(bid, bidderRequest) { } addUserId(bidData, bid.userId); + // TODO: is 'page' the right value here? does the fallback make sense? + bidData.u = bidderRequest?.refererInfo?.page || bidderRequest?.refererInfo?.topmostLocation; try { bidData.host = window.top.location.hostname; - bidData.u = config.getConfig('publisherDomain') || window.top.location.href; bidData.xr = 0; } catch (e) { bidData.host = window.location.hostname; - bidData.u = config.getConfig('publisherDomain') || bidderRequest.refererInfo.referrer || document.referrer || window.location.href; bidData.xr = 1; } @@ -314,17 +314,10 @@ function getRequestData(bid, bidderRequest) { } if (bidderRequest && bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.apiVersion == 1) { - Object.assign(bidData, { - gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, - euconsent: bidderRequest.gdprConsent.consentString - }); - } else if (bidderRequest.gdprConsent.apiVersion == 2) { - Object.assign(bidData, { - gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, - 'euconsent-v2': bidderRequest.gdprConsent.consentString - }); - } + Object.assign(bidData, { + gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, + 'euconsent-v2': bidderRequest.gdprConsent.consentString + }); } if (config.getConfig('coppa')) { @@ -338,9 +331,9 @@ function addUserId(bidData, userId) { bidData['eids'] = ''; _each(userId, (userIdObjectOrValue, userIdProviderKey) => { switch (userIdProviderKey) { - case 'haloId': - if (userIdObjectOrValue.haloId) { - bidData[userIdProviderKey + 'haloId'] = userIdObjectOrValue.haloId; + case 'hadronId': + if (userIdObjectOrValue.hadronId) { + bidData[userIdProviderKey + 'hadronId'] = userIdObjectOrValue.hadronId; } if (userIdObjectOrValue.auSeg) { bidData[userIdProviderKey + '_auSeg'] = userIdObjectOrValue.auSeg; @@ -373,11 +366,6 @@ function addUserId(bidData, userId) { : ('verizonMediaId,' + userIdObjectOrValue); } break; - case 'flocId': - if (userIdObjectOrValue.id) { - bidData['cid'] = userIdObjectOrValue.id; - } - break; default: bidData[userIdProviderKey] = userIdObjectOrValue; break; diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index f86faf3fe4d..eda4b6f579c 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -2,8 +2,8 @@ * Adapter to send bids to Undertone */ -import { deepAccess, parseUrl } from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; +import {deepAccess, parseUrl} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'undertone'; @@ -26,18 +26,6 @@ function getBidFloor(bidRequest, mediaType) { return (floor && floor.currency === 'USD' && floor.floor) || 0; } -function getCanonicalUrl() { - try { - let doc = window.top.document; - let element = doc.querySelector("link[rel='canonical']"); - if (element !== null) { - return element.href; - } - } catch (e) { - } - return null; -} - function extractDomainFromHost(pageHost) { let domain = null; try { @@ -111,8 +99,8 @@ export const spec = { 'x-ut-hb-params': [], 'commons': commons }; - const referer = bidderRequest.refererInfo.referer; - const canonicalUrl = getCanonicalUrl(); + const referer = bidderRequest.refererInfo.topmostLocation; + const canonicalUrl = bidderRequest.refererInfo.canonicalUrl; if (referer) { commons.referrer = referer; } diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js index 977e694acf7..9cbc79f3e61 100644 --- a/modules/unicornBidAdapter.js +++ b/modules/unicornBidAdapter.js @@ -64,9 +64,9 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { publisher: { id: String(deepAccess(validBidRequests[0], 'params.publisherId') || 0) }, - domain: window.location.hostname, - page: window.location.href, - ref: bidderRequest.refererInfo.referer + domain: bidderRequest.refererInfo.domain, + page: bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref }, device: { language: navigator.language, diff --git a/modules/uolBidAdapter.md b/modules/uolBidAdapter.md deleted file mode 100644 index 1d465c9a9c5..00000000000 --- a/modules/uolBidAdapter.md +++ /dev/null @@ -1,51 +0,0 @@ -# Overview - -``` -Module Name: UOL Project Bid Adapter -Module Type: Bidder Adapter -Maintainer: l-prebid@uolinc.com -``` - -# Description - -Connect to UOL Project's exchange for bids. - -For proper setup, please contact UOL Project's team at l-prebid@uolinc.com - -# Test Parameters -``` - var adUnits = [ - { - code: '/19968336/header-bid-tag-0', - mediaTypes: { - banner: { - sizes: [[300, 250],[300, 600]] - } - }, - bids: [{ - bidder: 'uol', - params: { - placementId: 1231244, - test: true, - cpmFactor: 2 - } - } - ] - }, - { - code: '/19968336/header-bid-tag-1', - mediaTypes: { - banner: { - sizes: [[970, 250],[728, 90]] - } - }, - bids: [{ - bidder: 'uol', - params: { - placementId: 1231242, - test: false - } - }] - } - ]; -``` diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 5d138795bd8..4b5fd55aa21 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -68,12 +68,14 @@ export const USER_IDS_CONFIG = { source: 'flashtalking.com', atype: 1, getValue: function(data) { - return data.uid + let value = ''; + if (data.DeviceID) { + value = data.DeviceID.join(','); + } + return value; }, getUidExt: function(data) { - if (data.ext) { - return data.ext; - } + return 'DeviceID'; } }, @@ -151,15 +153,25 @@ export const USER_IDS_CONFIG = { // merkleId 'merkleId': { - source: 'merkleinc.com', atype: 3, + getSource: function(data) { + if (data?.ext?.ssp) { + return `${data.ext.ssp}.merkleinc.com` + } + return 'merkleinc.com' + }, getValue: function(data) { return data.id; }, getUidExt: function(data) { - return (data && data.keyID) ? { - keyID: data.keyID - } : undefined; + if (data.keyID) { + return { + keyID: data.keyID + } + } + if (data.ext) { + return data.ext; + } } }, @@ -181,24 +193,12 @@ export const USER_IDS_CONFIG = { atype: 1 }, - // haloId (deprecated in 7.0, use hadronId) - 'haloId': { - source: 'audigent.com', - atype: 1 - }, - // quantcastId 'quantcastId': { source: 'quantcast.com', atype: 1 }, - // nextroll - 'nextrollId': { - source: 'nextroll.com', - atype: 1 - }, - // IDx 'idx': { source: 'idx.lat', @@ -245,12 +245,6 @@ export const USER_IDS_CONFIG = { } }, - // Akamai Data Activation Platform (DAP) - 'dapId': { - source: 'akamai.com', - atype: 1 - }, - 'deepintentId': { source: 'deepintent.com', atype: 3 @@ -305,6 +299,33 @@ export const USER_IDS_CONFIG = { source: 'impact-ad.jp', atype: 1 }, + + // 33across ID + '33acrossId': { + source: '33across.com', + atype: 1, + getValue: function(data) { + return data.envelope; + } + }, + + // tncId + 'tncid': { + source: 'thenewco.it', + atype: 3 + }, + + // Gravito MP ID + 'gravitompId': { + source: 'gravito.net', + atype: 1 + }, + + // cpexId + 'cpexId': { + source: 'czechadid.cz', + atype: 1 + } }; // this function will create an eid object for the given UserId sub-module @@ -312,7 +333,7 @@ function createEidObject(userIdData, subModuleKey) { const conf = USER_IDS_CONFIG[subModuleKey]; if (conf && userIdData) { let eid = {}; - eid.source = conf['source']; + eid.source = isFn(conf['getSource']) ? conf['getSource'](userIdData) : conf['source']; const value = isFn(conf['getValue']) ? conf['getValue'](userIdData) : userIdData; if (isStr(value)) { const uid = { id: value, atype: conf['atype'] }; @@ -342,10 +363,31 @@ function createEidObject(userIdData, subModuleKey) { // if any adapter does not want any particular userId to be passed then adapter can use Array.filter(e => e.source != 'tdid') export function createEidsArray(bidRequestUserId) { let eids = []; + for (const subModuleKey in bidRequestUserId) { if (bidRequestUserId.hasOwnProperty(subModuleKey)) { if (subModuleKey === 'pubProvidedId') { eids = eids.concat(bidRequestUserId['pubProvidedId']); + } else if (subModuleKey === 'ftrackId') { + // ftrack has multiple IDs so we add each one that exists + let eid = { + 'atype': 1, + 'id': (bidRequestUserId[subModuleKey]['DeviceID'] || []).join('|'), + 'ext': {} + } + for (let id in bidRequestUserId[subModuleKey]) { + eid.ext[id] = (bidRequestUserId[subModuleKey][id] || []).join('|'); + } + + eids.push(eid); + } else if (Array.isArray(bidRequestUserId[subModuleKey])) { + bidRequestUserId[subModuleKey].forEach((config, index, arr) => { + const eid = createEidObject(config, subModuleKey); + + if (eid) { + eids.push(eid); + } + }) } else { const eid = createEidObject(bidRequestUserId[subModuleKey], subModuleKey); if (eid) { diff --git a/modules/userId/eids.md b/modules/userId/eids.md index a61ef66c56c..83414cbe295 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -2,6 +2,13 @@ ``` userIdAsEids = [ + { + source: '33across.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }, { source: 'trustpid.com', uids: [{ @@ -68,7 +75,7 @@ userIdAsEids = [ { source: 'flashtalking.com', uids: [{ - id: 'some-random-id-value', + id: 'the-ids-object-stringified', atype: 1 }, @@ -147,14 +154,6 @@ userIdAsEids = [ }] }, - { - source: 'nextroll.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }, - { source: 'audigent.com', uids: [{ @@ -206,13 +205,6 @@ userIdAsEids = [ atype: 3 }] }, - { - source: 'akamai.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }, { source: 'admixer.net', uids: [{ @@ -240,6 +232,13 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 3 }] + }, + { + source: 'thenewco.it', + uids: [{ + id: 'some-random-id-value', + atype: 3 + }] } ] ``` diff --git a/modules/userId/index.js b/modules/userId/index.js index e656673befb..ecc494846ee 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -125,18 +125,13 @@ * @property {(function|undefined)} callback - function that will return an id */ -/** - * @typedef {Object} RefreshUserIdsOptions - * @property {(string[]|undefined)} submoduleNames - submodules to refresh - */ - import {find, includes} from '../../src/polyfill.js'; import {config} from '../../src/config.js'; import * as events from '../../src/events.js'; import {getGlobal} from '../../src/prebidGlobal.js'; import {gdprDataHandler} from '../../src/adapterManager.js'; import CONSTANTS from '../../src/constants.json'; -import {hook, module} from '../../src/hook.js'; +import {hook, module, ready as hooksReady} from '../../src/hook.js'; import {buildEidPermissions, createEidsArray, USER_IDS_CONFIG} from './eids.js'; import {getCoreStorageManager} from '../../src/storageManager.js'; import { @@ -156,6 +151,9 @@ import { timestamp, isEmpty } from '../../src/utils.js'; +import {getPPID as coreGetPPID} from '../../src/adserver.js'; +import {defer, GreedyPromise} from '../../src/utils/promise.js'; +import {hasPurpose1Consent} from '../../src/utils/gpdr.js'; const MODULE_NAME = 'User ID'; const COOKIE = 'cookie'; @@ -199,6 +197,8 @@ export let auctionDelay; /** @type {(string|undefined)} */ let ppidSource; +let configListener; + /** @param {Submodule[]} submodules */ export function setSubmoduleRegistry(submodules) { submoduleRegistry = submodules; @@ -339,26 +339,6 @@ function storedConsentDataMatchesConsentData(storedConsentData, consentData) { ); } -/** - * test if consent module is present, applies, and is valid for local storage or cookies (purpose 1) - * @param {ConsentData} consentData - * @returns {boolean} - */ -function hasGDPRConsent(consentData) { - if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) { - if (!consentData.consentString) { - return false; - } - if (consentData.apiVersion === 1 && deepAccess(consentData, 'vendorData.purposeConsents.1') === false) { - return false; - } - if (consentData.apiVersion === 2 && deepAccess(consentData, 'vendorData.purpose.consents.1') === false) { - return false; - } - } - return true; -} - /** * Find the root domain * @param {string|undefined} fullDomain @@ -426,7 +406,7 @@ function processSubmoduleCallbacks(submodules, cb) { }, submodules.length); } submodules.forEach(function (submodule) { - submodule.callback(function callbackCompleted(idObj) { + function callbackCompleted(idObj) { // if valid, id data should be saved to cookie/html storage if (idObj) { if (submodule.config.storage) { @@ -438,8 +418,13 @@ function processSubmoduleCallbacks(submodules, cb) { logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); } done(); - }); - + } + try { + submodule.callback(callbackCompleted); + } catch (e) { + logError(`Error in userID module '${submodule.submodule.name}':`, e); + done(); + } // clear callback, this prop is used to test if all submodule callbacks are complete below submodule.callback = undefined; }); @@ -473,8 +458,8 @@ function getSubmoduleId(submodules, sourceName) { return {}; } const submodule = submodules.filter(sub => isPlainObject(sub.idObj) && - Object.keys(sub.idObj).length && USER_IDS_CONFIG[Object.keys(sub.idObj)[0]].source === sourceName) - return !isEmpty(submodule) ? submodule[0].idObj : [] + Object.keys(sub.idObj).length && USER_IDS_CONFIG[Object.keys(sub.idObj)[0]]?.source === sourceName); + return !isEmpty(submodule) ? submodule[0].idObj : []; } /** @@ -519,56 +504,107 @@ function addIdDataToAdUnitBids(adUnits, submodules) { }); } -/** - * This is a common function that will initialize subModules if not already done and it will also execute subModule callbacks - */ -function initializeSubmodulesAndExecuteCallbacks(continueAuction) { - let delayed = false; +const INIT_CANCELED = {}; - // initialize submodules only when undefined - if (typeof initializedSubmodules === 'undefined') { - initializedSubmodules = initSubmodules(submodules, gdprDataHandler.getConsentData()); - if (initializedSubmodules.length) { - setPrebidServerEidPermissions(initializedSubmodules); - // list of submodules that have callbacks that need to be executed - const submodulesWithCallbacks = initializedSubmodules.filter(item => isFn(item.callback)); +function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { + const startInit = defer(); + const startCallbacks = defer(); + let cancel; + let initialized = false; - if (submodulesWithCallbacks.length) { - if (continueAuction && auctionDelay > 0) { - // delay auction until ids are available - delayed = true; - let continued = false; - const continueCallback = function () { - if (!continued) { - continued = true; - continueAuction(); - } - } - logInfo(`${MODULE_NAME} - auction delayed by ${auctionDelay} at most to fetch ids`); - - timeoutID = setTimeout(continueCallback, auctionDelay); - processSubmoduleCallbacks(submodulesWithCallbacks, continueCallback); - } else { - // wait for auction complete before processing submodule callbacks - events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() { - events.off(CONSTANTS.EVENTS.AUCTION_END, auctionEndHandler); - - // when syncDelay is zero, process callbacks now, otherwise delay process with a setTimeout - if (syncDelay > 0) { - setTimeout(function () { - processSubmoduleCallbacks(submodulesWithCallbacks); - }, syncDelay); - } else { - processSubmoduleCallbacks(submodulesWithCallbacks); - } - }); - } + function cancelAndTry(promise) { + if (cancel != null) { + cancel.reject(INIT_CANCELED); + } + cancel = defer(); + return GreedyPromise.race([promise, cancel.promise]); + } + + // grab a reference to global vars so that the promise chains remain isolated; + // multiple calls to `init` (from tests) might otherwise cause them to interfere with each other + let initModules = initializedSubmodules; + let allModules = submodules; + + function checkRefs(fn) { + // unfortunately tests have their own global state that needs to be guarded, so even if we keep ours tidy, + // we cannot let things like submodule callbacks run (they pollute things like the global `server` XHR mock) + return function(...args) { + if (initModules === initializedSubmodules && allModules === submodules) { + return fn(...args); } } } - if (continueAuction && !delayed) { - continueAuction(); + let done = cancelAndTry( + GreedyPromise.all([hooksReady, startInit.promise]) + .then(() => gdprDataHandler.promise) + .then(checkRefs((consentData) => { + initSubmodules(initModules, allModules, consentData); + })) + .then(() => startCallbacks.promise) + .then(checkRefs(() => { + const modWithCb = initModules.filter(item => isFn(item.callback)); + if (modWithCb.length) { + return new GreedyPromise((resolve) => processSubmoduleCallbacks(modWithCb, resolve)); + } + })) + ); + + /** + * with `ready` = true, starts initialization; with `refresh` = true, reinitialize submodules (optionally + * filtered by `submoduleNames`). + */ + return function ({refresh = false, submoduleNames = null, ready = false} = {}) { + if (ready && !initialized) { + initialized = true; + startInit.resolve(); + // submodule callbacks should run immediately if `auctionDelay` > 0, or `syncDelay` ms after the + // auction ends otherwise + if (auctionDelay > 0) { + startCallbacks.resolve(); + } else { + events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() { + events.off(CONSTANTS.EVENTS.AUCTION_END, auctionEndHandler); + delay(syncDelay).then(startCallbacks.resolve); + }); + } + } + if (refresh && initialized) { + done = cancelAndTry( + done + .catch(() => null) + .then(() => gdprDataHandler.promise) // fetch again in case a refresh was forced before this was resolved + .then(checkRefs((consentData) => { + const cbModules = initSubmodules( + initModules, + allModules.filter((sm) => submoduleNames == null || submoduleNames.includes(sm.submodule.name)), + consentData, + true + ).filter((sm) => { + return sm.callback != null; + }); + if (cbModules.length) { + return new GreedyPromise((resolve) => processSubmoduleCallbacks(cbModules, resolve)); + } + })) + ); + } + return done; + }; +} + +let initIdSystem; + +function getPPID() { + // userSync.ppid should be one of the 'source' values in getUserIdsAsEids() eg pubcid.org or id5-sync.com + const matchingUserId = ppidSource && (getUserIdsAsEids() || []).find(userID => userID.source === ppidSource); + if (matchingUserId && typeof deepAccess(matchingUserId, 'uids.0.id') === 'string') { + const ppidValue = matchingUserId.uids[0].id.replace(/[\W_]/g, ''); + if (ppidValue.length >= 32 && ppidValue.length <= 150) { + return ppidValue; + } else { + logWarn(`User ID - Googletag Publisher Provided ID for ${ppidSource} is not between 32 and 150 characters - ${ppidValue}`); + } } } @@ -581,28 +617,23 @@ function initializeSubmodulesAndExecuteCallbacks(continueAuction) { * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export function requestBidsHook(fn, reqBidsConfigObj) { - // initialize submodules only when undefined - initializeSubmodulesAndExecuteCallbacks(function () { +export function requestBidsHook(fn, reqBidsConfigObj, {delay = GreedyPromise.timeout} = {}) { + GreedyPromise.race([ + getUserIdsAsync(), + delay(auctionDelay) + ]).then(() => { // pass available user id data to bid adapters addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules); - - // userSync.ppid should be one of the 'source' values in getUserIdsAsEids() eg pubcid.org or id5-sync.com - const matchingUserId = ppidSource && (getUserIdsAsEids() || []).find(userID => userID.source === ppidSource); - if (matchingUserId && typeof deepAccess(matchingUserId, 'uids.0.id') === 'string') { - const ppidValue = matchingUserId.uids[0].id.replace(/[\W_]/g, ''); - if (ppidValue.length >= 32 && ppidValue.length <= 150) { - if (isGptPubadsDefined()) { - window.googletag.pubads().setPublisherProvidedId(ppidValue); - } else { - window.googletag = window.googletag || {}; - window.googletag.cmd = window.googletag.cmd || []; - window.googletag.cmd.push(function() { - window.googletag.pubads().setPublisherProvidedId(ppidValue); - }); - } + const ppid = getPPID(); + if (ppid) { + if (isGptPubadsDefined()) { + window.googletag.pubads().setPublisherProvidedId(ppid); } else { - logWarn(`User ID - Googletag Publisher Provided ID for ${ppidSource} is not between 32 and 150 characters - ${ppidValue}`); + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(function() { + window.googletag.pubads().setPublisherProvidedId(ppid); + }); } } @@ -616,9 +647,7 @@ export function requestBidsHook(fn, reqBidsConfigObj) { * Simple use case will be passing these UserIds to A9 wrapper solution */ function getUserIds() { - // initialize submodules only when undefined - initializeSubmodulesAndExecuteCallbacks(); - return getCombinedSubmoduleIds(initializedSubmodules); + return getCombinedSubmoduleIds(initializedSubmodules) } /** @@ -626,9 +655,7 @@ function getUserIds() { * Simple use case will be passing these UserIds to A9 wrapper solution */ function getUserIdsAsEids() { - // initialize submodules only when undefined - initializeSubmodulesAndExecuteCallbacks(); - return createEidsArray(getCombinedSubmoduleIds(initializedSubmodules)); + return createEidsArray(getUserIds()) } /** @@ -637,33 +664,33 @@ function getUserIdsAsEids() { */ function getUserIdsAsEidBySource(sourceName) { - initializeSubmodulesAndExecuteCallbacks(); return createEidsArray(getSubmoduleId(initializedSubmodules, sourceName))[0]; -}; +} /** * This function will be exposed in global-name-space so that userIds for a source can be exposed * Sample use case is exposing this function to ESP */ function getEncryptedEidsForSource(source, encrypt, customFunction) { - let eidsSignals = {}; - - if (isFn(customFunction)) { - logInfo(`${MODULE_NAME} - Getting encrypted signal from custom function : ${customFunction.name} & source : ${source} `); - // Publishers are expected to define a common function which will be proxy for signal function. - const customSignals = customFunction(source); - eidsSignals[source] = customSignals ? encryptSignals(customSignals) : null; // by default encrypt using base64 to avoid JSON errors - } else { - // initialize signal with eids by default - const eid = getUserIdsAsEidBySource(source); - logInfo(`${MODULE_NAME} - Getting encrypted signal for eids :${JSON.stringify(eid)}`); - if (!isEmpty(eid)) { - eidsSignals[eid.source] = encrypt === true ? encryptSignals(eid) : eid.uids[0].id; // If encryption is enabled append version (1||) and encrypt entire object + return initIdSystem().then(() => { + let eidsSignals = {}; + + if (isFn(customFunction)) { + logInfo(`${MODULE_NAME} - Getting encrypted signal from custom function : ${customFunction.name} & source : ${source} `); + // Publishers are expected to define a common function which will be proxy for signal function. + const customSignals = customFunction(source); + eidsSignals[source] = customSignals ? encryptSignals(customSignals) : null; // by default encrypt using base64 to avoid JSON errors + } else { + // initialize signal with eids by default + const eid = getUserIdsAsEidBySource(source); + logInfo(`${MODULE_NAME} - Getting encrypted signal for eids :${JSON.stringify(eid)}`); + if (!isEmpty(eid)) { + eidsSignals[eid.source] = encrypt === true ? encryptSignals(eid) : eid.uids[0].id; // If encryption is enabled append version (1||) and encrypt entire object + } } - } - const promise = Promise.resolve(eidsSignals[source]); - logInfo(`${MODULE_NAME} - Fetching encrypted eids: ${eidsSignals[source]}`); - return promise; + logInfo(`${MODULE_NAME} - Fetching encrypted eids: ${eidsSignals[source]}`); + return eidsSignals[source]; + }) } function encryptSignals(signals, version = 1) { @@ -705,57 +732,47 @@ function registerSignalSources() { } /** -* This function will be exposed in the global-name-space so that userIds can be refreshed after initialization. -* @param {RefreshUserIdsOptions} options -*/ -function refreshUserIds(options, callback) { - let submoduleNames = options ? options.submoduleNames : null; - if (!submoduleNames) { - submoduleNames = []; - } - - initializeSubmodulesAndExecuteCallbacks(function() { - let consentData = gdprDataHandler.getConsentData() - - // gdpr consent with purpose one is required, otherwise exit immediately - let {userIdModules, hasValidated} = validateGdprEnforcement(submodules, consentData); - if (!hasValidated && !hasGDPRConsent(consentData)) { - logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); - return; - } - - // we always want the latest consentData stored, even if we don't execute any submodules - const storedConsentData = getStoredConsentData(); - setStoredConsentData(consentData); - - let callbackSubmodules = []; - for (let submodule of userIdModules) { - if (submoduleNames.length > 0 && - submoduleNames.indexOf(submodule.submodule.name) === -1) { - continue; - } - - logInfo(`${MODULE_NAME} - refreshing ${submodule.submodule.name}`); - populateSubmoduleId(submodule, consentData, storedConsentData, true); - updateInitializedSubmodules(submodule); - - if (initializedSubmodules.length) { - setPrebidServerEidPermissions(initializedSubmodules); - } - - if (isFn(submodule.callback)) { - callbackSubmodules.push(submodule); + * Force (re)initialization of ID submodules. + * + * This will force a refresh of the specified ID submodules regardless of `auctionDelay` / `syncDelay` settings, and + * return a promise that resolves to the same value as `getUserIds()` when the refresh is complete. + * If a refresh is already in progress, it will be canceled (rejecting promises returned by previous calls to `refreshUserIds`). + * + * @param submoduleNames? submodules to refresh. If omitted, refresh all submodules. + * @param callback? called when the refresh is complete + */ +function refreshUserIds({submoduleNames} = {}, callback) { + return initIdSystem({refresh: true, submoduleNames}) + .then(() => { + if (callback && isFn(callback)) { + callback(); } - } + return getUserIds(); + }); +} - if (callbackSubmodules.length > 0) { - processSubmoduleCallbacks(callbackSubmodules); - } +/** + * @returns a promise that resolves to the same value as `getUserIds()`, but only once all ID submodules have completed + * initialization. This can also be used to synchronize calls to other ID accessors, e.g. + * + * ``` + * pbjs.getUserIdsAsync().then(() => { + * const eids = pbjs.getUserIdsAsEids(); // guaranteed to be completely initialized at this point + * }); + * ``` + */ - if (callback) { - callback(); - } - }); +function getUserIdsAsync() { + return initIdSystem().then( + () => getUserIds(), + (e) => + e === INIT_CANCELED + // there's a pending refresh - because GreedyPromise runs this synchronously, we are now in the middle + // of canceling the previous init, before the refresh logic has had a chance to run. + // Use a "normal" Promise to clear the stack and let it complete (or this will just recurse infinitely) + ? Promise.resolve().then(getUserIdsAsync) + : GreedyPromise.reject(e) + ); } /** @@ -816,15 +833,10 @@ function populateSubmoduleId(submodule, consentData, storedConsentData, forceRef } } -/** - * @param {SubmoduleContainer[]} submodules - * @param {ConsentData} consentData - * @returns {SubmoduleContainer[]} initialized submodules - */ -function initSubmodules(submodules, consentData) { +function initSubmodules(dest, submodules, consentData, forceRefresh = false) { // gdpr consent with purpose one is required, otherwise exit immediately let { userIdModules, hasValidated } = validateGdprEnforcement(submodules, consentData); - if (!hasValidated && !hasGDPRConsent(consentData)) { + if (!hasValidated && !hasPurpose1Consent(consentData)) { logWarn(`${MODULE_NAME} - gdpr permission not valid for local storage or cookies, exit module`); return []; } @@ -833,25 +845,34 @@ function initSubmodules(submodules, consentData) { const storedConsentData = getStoredConsentData(); setStoredConsentData(consentData); - return userIdModules.reduce((carry, submodule) => { - populateSubmoduleId(submodule, consentData, storedConsentData, false); - carry.push(submodule); + const initialized = userIdModules.reduce((carry, submodule) => { + try { + populateSubmoduleId(submodule, consentData, storedConsentData, forceRefresh); + carry.push(submodule); + } catch (e) { + logError(`Error in userID module '${submodule.submodule.name}':`, e); + } return carry; }, []); + if (initialized.length) { + setPrebidServerEidPermissions(initialized); + } + initialized.forEach(updateInitializedSubmodules.bind(null, dest)); + return initialized; } -function updateInitializedSubmodules(submodule) { +function updateInitializedSubmodules(dest, submodule) { let updated = false; - for (let i = 0; i < initializedSubmodules.length; i++) { - if (submodule.config.name.toLowerCase() === initializedSubmodules[i].config.name.toLowerCase()) { + for (let i = 0; i < dest.length; i++) { + if (submodule.config.name.toLowerCase() === dest[i].config.name.toLowerCase()) { updated = true; - initializedSubmodules[i] = submodule; + dest[i] = submodule; break; } } if (!updated) { - initializedSubmodules.push(submodule); + dest.push(submodule); } } @@ -898,10 +919,13 @@ function updateSubmodules() { return; } // do this to avoid reprocessing submodules + // TODO: the logic does not match the comment - addedSubmodules is always a copy of submoduleRegistry + // (if it did it would not be correct - it's not enough to find new modules, as others may have been removed or changed) const addedSubmodules = submoduleRegistry.filter(i => !find(submodules, j => j.name === i.name)); + submodules.splice(0, submodules.length); // find submodule and the matching configuration, if found create and append a SubmoduleContainer - submodules = addedSubmodules.map(i => { + addedSubmodules.map(i => { const submoduleConfig = find(configs, j => j.name && (j.name.toLowerCase() === i.name.toLowerCase() || (i.aliasName && j.name.toLowerCase() === i.aliasName.toLowerCase()))); if (submoduleConfig && i.name !== submoduleConfig.name) submoduleConfig.name = i.name; @@ -912,11 +936,13 @@ function updateSubmodules() { callback: undefined, idObj: undefined } : null; - }).filter(submodule => submodule !== null); + }).filter(submodule => submodule !== null) + .forEach((sm) => submodules.push(sm)); if (!addedUserIdHook && submodules.length) { // priority value 40 will load after consentManagement with a priority of 50 getGlobal().requestBids.before(requestBidsHook, 40); + coreGetPPID.after((next) => next(getPPID())); logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); addedUserIdHook = true; } @@ -930,6 +956,17 @@ export function attachIdSystem(submodule) { if (!find(submoduleRegistry, i => i.name === submodule.name)) { submoduleRegistry.push(submodule); updateSubmodules(); + // TODO: a test case wants this to work even if called after init (the setConfig({userId})) + // so we trigger a refresh. But is that even possible outside of tests? + initIdSystem({refresh: true, submoduleNames: [submodule.name]}); + } +} + +function normalizePromise(fn) { + // for public methods that return promises, make sure we return a "normal" one - to avoid + // exposing confusing stack traces + return function() { + return Promise.resolve(fn.apply(this, arguments)); } } @@ -938,12 +975,17 @@ export function attachIdSystem(submodule) { * so a callback is added to fire after the consentManagement module. * @param {{getConfig:function}} config */ -export function init(config) { +export function init(config, {delay = GreedyPromise.timeout} = {}) { ppidSource = undefined; submodules = []; configRegistry = []; addedUserIdHook = false; - initializedSubmodules = undefined; + initializedSubmodules = []; + initIdSystem = idSystemInitializer({delay}); + if (configListener != null) { + configListener(); + } + submoduleRegistry = []; // list of browser enabled storage types validStorageTypes = [ @@ -962,7 +1004,7 @@ export function init(config) { } // listen for config userSyncs to be set - config.getConfig('userSync', conf => { + configListener = config.getConfig('userSync', conf => { // Note: support for 'usersync' was dropped as part of Prebid.js 4.0 const userSync = conf.userSync; ppidSource = userSync.ppid; @@ -971,15 +1013,17 @@ export function init(config) { syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; auctionDelay = isNumber(userSync.auctionDelay) ? userSync.auctionDelay : NO_AUCTION_DELAY; updateSubmodules(); + initIdSystem({ready: true}); } }); // exposing getUserIds function in global-name-space so that userIds stored in Prebid can be used by external codes. (getGlobal()).getUserIds = getUserIds; (getGlobal()).getUserIdsAsEids = getUserIdsAsEids; - (getGlobal()).getEncryptedEidsForSource = getEncryptedEidsForSource; + (getGlobal()).getEncryptedEidsForSource = normalizePromise(getEncryptedEidsForSource); (getGlobal()).registerSignalSources = registerSignalSources; - (getGlobal()).refreshUserIds = refreshUserIds; + (getGlobal()).refreshUserIds = normalizePromise(refreshUserIds); + (getGlobal()).getUserIdsAsync = normalizePromise(getUserIdsAsync); (getGlobal()).getUserIdsAsEidBySource = getUserIdsAsEidBySource; } diff --git a/modules/userId/userId.md b/modules/userId/userId.md index ee49211e4cb..4a6a9b515a8 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -6,6 +6,17 @@ Example showing `cookie` storage for user id data for each of the submodules pbjs.setConfig({ userSync: { userIds: [{ + name: "33acrossId", + storage: { + type: "cookie", + name: "33acrossId", + expires: 90, + refreshInSeconds: 8*3600 + }, + params: { + pid: "0010b00002GYU4eBAH" // Example ID + } + }, { name: "pubCommonId", storage: { type: "cookie", @@ -62,16 +73,6 @@ pbjs.setConfig({ // Replace partner with comma-separated (if more than one) Parrable Partner Client ID(s) for Parrable-aware bid adapters in use partner: "30182847-e426-4ff9-b2b5-9ca1324ea09b" } - },{ - 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": "..." }' - } },{ name: 'identityLink', params: { @@ -100,6 +101,8 @@ pbjs.setConfig({ name: '_criteoId', expires: 1 } + }, { + name: "cpexId" }, { name: 'mwOpenLinkId', params: { @@ -136,11 +139,6 @@ pbjs.setConfig({ name: '__adm__admixer', expires: 30 } - },{ - name: 'flocId', - params: { - token: "Registered token or default sharedid.org token" // Default sharedid.org token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9" - } },{ name: "kpuid", params:{ @@ -153,6 +151,9 @@ pbjs.setConfig({ }, { name: "dacId" + }, + { + name: "gravitompId" } ], syncDelay: 5000, @@ -166,17 +167,16 @@ Example showing `localStorage` for user id data for some submodules ``` pbjs.setConfig({ userSync: { - userIds: [ - { - name: 'trustpid', - params: { - maxDelayTime: 2500 - }, - bidders: ['adform'], + userIds: [{ + name: "33acrossId", storage: { - type: 'html5', - name: 'trustpid', - expires: 60 + type: "html5", + name: "33acrossId", + expires: 90, + refreshInSeconds: 8*3600 + }, + params: { + pid: "0010b00002GYU4eBAH" // Example ID } }, { name: "unifiedId", @@ -239,11 +239,6 @@ pbjs.setConfig({ expires: 90, // Expiration in days refreshInSeconds: 8*3600 // User Id cache lifetime in seconds, defaulting to 'expires' }, - }, { - name: 'nextrollId', - params: { - partnerId: "1009", // Set your real NextRoll partner ID here for production - } }, { name: 'criteo', storage: { // It is best not to specify this parameter since the module needs to be called as many times as possible @@ -278,24 +273,12 @@ pbjs.setConfig({ expires: 30 } },{ - name: 'flocId', - params: { - token: "Registered token or default sharedid.org token" // Default sharedid.org token: "A3dHTSoNUMjjERBLlrvJSelNnwWUCwVQhZ5tNQ+sll7y+LkPPVZXtB77u2y7CweRIxiYaGwGXNlW1/dFp8VMEgIAAAB+eyJvcmlnaW4iOiJodHRwczovL3NoYXJlZGlkLm9yZzo0NDMiLCJmZWF0dXJlIjoiSW50ZXJlc3RDb2hvcnRBUEkiLCJleHBpcnkiOjE2MjYyMjA3OTksImlzU3ViZG9tYWluIjp0cnVlLCJpc1RoaXJkUGFydHkiOnRydWV9" - } - },{ name: "deepintentId", storage: { type: "html5", name: "_dpes_id", expires: 90 } - },{ - name: "deepintentId", - storage: { - type: "cookie", - name: "_dpes_id", - expires: 90 - } },{ name: "kpuid", params:{ @@ -373,3 +356,20 @@ pbjs.setConfig({ } }); ``` +``` + +Example showing how to configure a `params` object to pass directly to bid adapters + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'tncId', + params: { + providerId: "c8549079-f149-4529-a34b-3fa91ef257d1" + } + }], + syncDelay: 5000 + } +}); +``` diff --git a/modules/userIdTargeting.js b/modules/userIdTargeting.js deleted file mode 100644 index b7fd137779b..00000000000 --- a/modules/userIdTargeting.js +++ /dev/null @@ -1,63 +0,0 @@ -import {config} from '../src/config.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import CONSTANTS from '../src/constants.json'; -import * as events from '../src/events.js'; -import { isStr, isPlainObject, isBoolean, isFn, hasOwn, logInfo } from '../src/utils.js'; - -const MODULE_NAME = 'userIdTargeting'; -const GAM = 'GAM'; -const GAM_KEYS_CONFIG = 'GAM_KEYS'; - -export function userIdTargeting(userIds, config) { - if (!isPlainObject(config)) { - logInfo(MODULE_NAME + ': Invalid config found, not sharing userIds externally.'); - return; - } - - const PUB_GAM_KEYS = isPlainObject(config[GAM_KEYS_CONFIG]) ? config[GAM_KEYS_CONFIG] : {}; - let SHARE_WITH_GAM = isBoolean(config[GAM]) ? config[GAM] : false; - let GAM_API; - - if (!SHARE_WITH_GAM) { - logInfo(MODULE_NAME + ': Not enabled for ' + GAM); - } else if (window.googletag && isFn(window.googletag.pubads) && hasOwn(window.googletag.pubads(), 'setTargeting') && isFn(window.googletag.pubads().setTargeting)) { - GAM_API = window.googletag.pubads().setTargeting; - } else { - window.googletag = window.googletag || {}; - window.googletag.cmd = window.googletag.cmd || []; - GAM_API = function (key, value) { - window.googletag.cmd.push(function () { - window.googletag.pubads().setTargeting(key, value); - }); - }; - } - - Object.keys(userIds).forEach(function(key) { - if (userIds[key]) { - // PUB_GAM_KEYS: { "tdid": '' } means the publisher does not want to send the tdid to GAM - if (SHARE_WITH_GAM && PUB_GAM_KEYS[key] !== '') { - let uidStr; - if (isStr(userIds[key])) { - uidStr = userIds[key]; - } else if (isPlainObject(userIds[key])) { - uidStr = JSON.stringify(userIds[key]) - } else { - logInfo(MODULE_NAME + ': ' + key + ' User ID is not an object or a string.'); - return; - } - GAM_API( - (hasOwn(PUB_GAM_KEYS, key) ? PUB_GAM_KEYS[key] : key), - [ uidStr ] - ); - } - } - }); -} - -export function init(config) { - events.on(CONSTANTS.EVENTS.AUCTION_END, function() { - userIdTargeting((getGlobal()).getUserIds(), config.getConfig(MODULE_NAME)); - }) -} - -init(config) diff --git a/modules/userIdTargeting.md b/modules/userIdTargeting.md deleted file mode 100644 index 340c1b6abf2..00000000000 --- a/modules/userIdTargeting.md +++ /dev/null @@ -1,37 +0,0 @@ -## userIdTargeting Module -- This module works with userId module. -- This module is used to pass userIds to GAM in targeting so that user ids can be used to pass in Google Exchange Bidding or can be used for targeting in GAM. - -## Sample config -``` -pbjs.setConfig({ - - // your existing userIds config - - userSync: { - userIds: [{...}, ...] - }, - - // new userIdTargeting config - - userIdTargeting: { - "GAM": true, - "GAM_KEYS": { - "tdid": "TTD_ID" // send tdid as TTD_ID - } - } -}); -``` - -## Config options -- GAM: is required to be set to true if a publisher wants to send UserIds as targeting in GAM call. This module uses ``` googletag.pubads().setTargeting('key-name', ['value']) ``` API to set GAM targeting. -- GAM_KEYS: is an optional config object to be used with ``` "GAM": true ```. If not passed then all UserIds are passed with respective key-name used in UserIds object. -If a publisher wants to pass ```UserId.tdid``` as TTD_ID in targeting then set ``` GAM_KEYS: { "tdid": "TTD_ID" }``` -If a publisher does not wants to pass ```UserId.tdid``` but wants to pass other Ids in UserId tthen set ``` GAM_KEYS: { "tdid": "" }``` - -## Including this module in Prebid -``` $ gulp build --modules=userId,userIdTargeting,pubmaticBidAdapter ``` - -## Notes -- We can add support for other external systems like GAM in future -- We have not added support for A9/APSTag as it is called in parallel with Prebid. This module executes when ```pbjs.requestBids``` is called, in practice, call to A9 is expected to execute in paralle to Prebid thus we have not covered A9 here. For sending Uids in A9, one will need to set those Ids in params key in the object passed to ```apstag.init```, ```pbjs.getUserIds``` can be used for the same. diff --git a/modules/validationFpdModule/index.js b/modules/validationFpdModule/index.js index 2db170c1bd1..8771e50b156 100644 --- a/modules/validationFpdModule/index.js +++ b/modules/validationFpdModule/index.js @@ -2,11 +2,10 @@ * This module sets default values and validates ortb2 first part data * @module modules/firstPartyData */ -import { config } from '../../src/config.js'; -import { isEmpty, isNumber, logWarn, deepAccess } from '../../src/utils.js'; -import { ORTB_MAP } from './config.js'; -import { submodule } from '../../src/hook.js'; -import { getStorageManager } from '../../src/storageManager.js'; +import {deepAccess, isEmpty, isNumber, logWarn} from '../../src/utils.js'; +import {ORTB_MAP} from './config.js'; +import {submodule} from '../../src/hook.js'; +import {getStorageManager} from '../../src/storageManager.js'; const STORAGE = getStorageManager(); let optout; @@ -192,29 +191,16 @@ export function validateFpd(fpd, path = '', parent = '') { * Run validation on global and bidder config data for ortb2 */ function runValidations(data) { - let conf = validateFpd(data); - - let bidderDuplicate = { ...config.getBidderConfig() }; - - Object.keys(bidderDuplicate).forEach(bidder => { - let modConf = Object.keys(bidderDuplicate[bidder]).reduce((res, key) => { - let valid = (key !== 'ortb2') ? bidderDuplicate[bidder][key] : validateFpd(bidderDuplicate[bidder][key]); - - if (valid) res[key] = valid; - - return res; - }, {}); - - if (Object.keys(modConf).length) config.setBidderConfig({ bidders: [bidder], config: modConf }); - }); - - return conf; + return { + global: validateFpd(data.global), + bidder: Object.fromEntries(Object.entries(data.bidder).map(([bidder, conf]) => [bidder, validateFpd(conf)])) + } } /** * 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, data) { // Checks for existsnece of pubcid optout cookie/storage // if exists, filters user data out optout = (STORAGE.cookiesAreEnabled() && STORAGE.getCookie('_pubcid_optout')) || @@ -227,7 +213,7 @@ export function initSubmodule(fpdConf, data) { export const validationSubmodule = { name: 'validation', queue: 1, - init: initSubmodule + processFpd } submodule('firstPartyData', validationSubmodule) diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index 40e3b3322a7..e149a56f988 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -37,7 +37,8 @@ export const spec = { placementId: bidRequest.params.placementId, sizes: sizes, bidId: bidRequest.bidId, - referer: bidderRequest.refererInfo.referer, + // TODO: is 'page' the right value here? + referer: bidderRequest.refererInfo.page, id: bidRequest.auctionId, mediaType: bidRequest.mediaTypes.video ? 'video' : 'banner' }; diff --git a/modules/ventes.md b/modules/ventes.md deleted file mode 100644 index 5f0f571ecd8..00000000000 --- a/modules/ventes.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -layout: bidder -title: ventes -description: Prebid ventes Bidder Adapter -pbjs: false -biddercode: ventes -gdpr_supported: false -usp_supported: false -media_types: banner -coppa_supported: false -schain_supported: false -dchain_supported: false -prebid_member: false ---- - -### BidParams -{: .table .table-bordered .table-striped } -| Name | Scope | Description | Example | Type | -|-----------------|----------|-----------------------------------------------------------|----------------------------------------------|---------------| -| `placementId` | required | Placement ID from Ventes Avenues | `'VA-062-0013-0183'` | `string` | -| `publisherId` | required | Publisher ID from Ventes Avenues | `'VA-062'` | `string` | -| `user` | optional | Object that specifies information about an external user. | `user: { age: 25, gender: 0, dnt: true}` | `object` | -| `app` | optional | Object containing mobile app parameters. | `app : { id: 'app-id'}` | `object` | - -#### User Object - -{: .table .table-bordered .table-striped } -| Name | Description | Example | Type | -|-------------------|-------------------------------------------------------------------------------------------|-----------------------|-----------------------| -| `age` | The age of the user. | `35` | `integer` | -| `externalUid` | Specifies a string that corresponds to an external user ID for this user. | `'1234567890abcdefg'` | `string` | -| `segments` | Specifies the segments to which the user belongs. | `[1, 2]` | `Array` | -| `gender` | Specifies the gender of the user. Allowed values: Unknown: `0`; Male: `1`; Female: `2` | `1` | `integer` | -| `dnt` | Do not track flag. Indicates if tracking cookies should be disabled for this auction | `true` | `boolean` | -| `language` | Two-letter ANSI code for this user's language. | `EN` | `string` | - - -### Ad Unit Setup for Banner -```javascript -var adUnits = [ -{ - code: 'test-hb-ad-11111-1', - mediaTypes: { - banner: { - sizes: [ - [300, 250] - ] - } - }, - bids: [{ - bidder: 'ventes', - params: { - placementId: 'VA-062-0013-0183', - publisherId: '5cebea3c9eea646c7b623d5e', - IABCategories: "['IAB1', 'IAB5']", - device:{ - ip: '123.145.167.189', - ifa:"AEBE52E7-03EE-455A-B3C4-E57283966239", - }, - app: { - id: "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA", - name: "Yahoo Weather", - bundle: 'com.kiloo.subwaysurf', - storeurl: 'https://play.google.com/store/apps/details?id=com.kiloo.subwaysurf&hl=en', - domain: 'somoaudience.com', - } - } - }] - } -] -``` diff --git a/modules/ventesBidAdapter.js b/modules/ventesBidAdapter.js index 42292ddaed3..787db1a7831 100644 --- a/modules/ventesBidAdapter.js +++ b/modules/ventesBidAdapter.js @@ -4,9 +4,7 @@ import {find} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; const BID_METHOD = 'POST'; -const BIDDER_URL = 'http://13.234.201.146:8088/va/ad'; - -const DOMAIN_REGEX = new RegExp('//([^/]*)'); +const BIDDER_URL = 'https://ad.ventesavenues.in/va/ad'; function groupBy(values, key) { const groups = values.reduce((acc, value) => { @@ -69,26 +67,16 @@ function validateParameters(parameters) { return true; } -function extractSiteDomainFromURL(url) { - if (!url || !isStr(url)) return null; - - const domain = url.match(DOMAIN_REGEX); - - if (isArray(domain) && domain.length === 2) return domain[1]; - - return null; -} - function generateSiteFromAdUnitContext(bidRequests, adUnitContext) { if (!adUnitContext || !adUnitContext.refererInfo) return null; - const domain = extractSiteDomainFromURL(adUnitContext.refererInfo.referer); + const domain = adUnitContext.refererInfo.domain; const publisherId = bidRequests[0].params.publisherId; if (!domain) return null; return { - page: adUnitContext.refererInfo.referer, + page: adUnitContext.refererInfo.page, domain: domain, name: domain, publisher: { diff --git a/modules/ventesBidAdapter.md b/modules/ventesBidAdapter.md index c79ef080cd1..5b75d48b90e 100644 --- a/modules/ventesBidAdapter.md +++ b/modules/ventesBidAdapter.md @@ -2,7 +2,7 @@ layout: bidder title: ventes description: Prebid ventes Bidder Adapter -pbjs: false +pbjs: true biddercode: ventes gdpr_supported: false usp_supported: false diff --git a/modules/vertamediaBidAdapter.md b/modules/vertamediaBidAdapter.md deleted file mode 100644 index 6b1265fa792..00000000000 --- a/modules/vertamediaBidAdapter.md +++ /dev/null @@ -1,65 +0,0 @@ -# Overview - -**Module Name**: VertaMedia Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: support@verta.media - -# Description - -Get access to multiple demand partners across VertaMedia AdExchange and maximize your yield with VertaMedia header bidding adapter. - -VertaMedia header bidding adapter connects with VertaMedia demand sources in order to fetch bids. -This adapter provides a solution for accessing Video demand and display demand - - -# Test Parameters -``` - var adUnits = [ - - // Video instream adUnit - { - code: 'div-test-div', - sizes: [[640, 480]], - mediaTypes: { - video: { - context: 'instream' - } - }, - bids: [{ - bidder: 'vertamedia', - params: { - aid: 331133 - } - }] - }, - - // Video outstream adUnit - { - code: 'outstream-test-div', - sizes: [[640, 480]], - mediaTypes: { - video: { - context: 'outstream' - } - }, - bids: [{ - bidder: 'vertamedia', - params: { - aid: 331133 - } - }] - }, - - // Banner adUnit - { - code: 'div-test-div', - sizes: [[300, 250]], - bids: [{ - bidder: 'vertamedia', - params: { - aid: 350975 - } - }] - } - ]; -``` diff --git a/modules/vertozBidAdapter.md b/modules/vertozBidAdapter.md deleted file mode 100644 index 100492da58b..00000000000 --- a/modules/vertozBidAdapter.md +++ /dev/null @@ -1,31 +0,0 @@ -# Overview - -``` -Module Name: Vertoz Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid-team@vertoz.com -``` - -# Description - -Connects to Vertoz exchange for bids. -Vertoz Bidder adapter supports Banner ads. -Use bidder code ```vertoz``` for all Vertoz traffic. - -# Test Parameters -``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - sizes: [[300, 250], [300,600]], // a display size(s) - bids: [{ - bidder: 'vertoz', - params: { - placementId: 'VZ-HB-B784382V6C6G3C' - } - }] - }, -]; -``` - diff --git a/modules/viBidAdapter.md b/modules/viBidAdapter.md deleted file mode 100644 index 2608ccc4adb..00000000000 --- a/modules/viBidAdapter.md +++ /dev/null @@ -1,42 +0,0 @@ -# Overview - -``` -Module Name: vi bid adapter -Module Type: Bidder adapter -Maintainer: support@vi.ai -``` - -# Description - -The video intelligence (vi) adapter integration to the Prebid library. -Connects to vi’s demand sources. -There should be only one ad unit with vi bid adapter on each single page. - -# Test Parameters - -``` -var adUnits = [{ - code: 'div-0', - sizes: [[320, 480]], - bids: [{ - bidder: 'vi', - params: { - pubId: 'sb_test', - lang: 'en-US', - cat: 'IAB1', - bidFloor: 0.05 //optional - } - }] -}]; -``` - -# Parameters - -| Name | Scope | Description | Example | -| :------------ | :------- | :---------------------------------------------- | :--------------------------------- | -| `pubId` | required | Publisher ID, provided by vi | 'sb_test' | -| `lang` | required | Ad language, in ISO 639-1 language code format | 'en-US', 'es-ES', 'de' | -| `cat` | required | Ad IAB category (top-level or subcategory), single one supported | 'IAB1', 'IAB9-1' | -| `bidFloor` | optional | Lowest value of expected bid price | 0.001 | -| `useSizes` | optional | Specifies from which section of the config sizes are taken, possible values are 'banner', 'video'. If omitted, sizes from both sections are merged. | 'banner' | - diff --git a/modules/vibrantmediaBidAdapter.js b/modules/vibrantmediaBidAdapter.js index b6fe51c43bc..0613f722af8 100644 --- a/modules/vibrantmediaBidAdapter.js +++ b/modules/vibrantmediaBidAdapter.js @@ -6,13 +6,14 @@ * Note: Only BANNER and VIDEO are currently supported by the prebid server. */ -import {logError, logInfo} from '../src/utils.js'; +import {logError, triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {OUTSTREAM} from '../src/video.js'; const BIDDER_CODE = 'vibrantmedia'; const VIBRANT_MEDIA_PREBID_URL = 'https://prebid.intellitxt.com/prebid'; +const VALID_PIXEL_URL_REGEX = /^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+([/?].*)?$/; const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; /** @@ -51,6 +52,10 @@ const isBaseUrl = function(url) { return (endOfDomain === -1) || (endOfDomain === (urlMinusScheme.length - 1)); }; +const isValidPixelUrl = function (candidateUrl) { + return VALID_PIXEL_URL_REGEX.test(candidateUrl); +}; + /** * Returns transformed bid requests that are in a format native to the prebid server. * @@ -213,7 +218,9 @@ export const spec = { * @param {*} bidData the data associated with the won bid. See example above for data format. */ onBidWon: function(bidData) { - logInfo('Bid won: ' + JSON.stringify(bidData)); + if (bidData && bidData.meta && isValidPixelUrl(bidData.meta.wp)) { + triggerPixel(`${bidData.meta.wp}${bidData.status}`); + } } }; diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index cf252bda2dc..3933d7632b6 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -15,7 +15,6 @@ const SESSION_ID_KEY = 'vidSid'; export const SUPPORTED_ID_SYSTEMS = { 'britepoolid': 1, 'criteoId': 1, - 'digitrustid': 1, 'id5id': 1, 'idl_env': 1, 'lipb': 1, @@ -129,7 +128,8 @@ function appendUserIdsToRequestPayload(payloadRef, userIds) { } function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.referer; + // TODO: does the fallback make sense here? + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; const requests = []; validBidRequests.forEach(validBidRequest => { const sizes = parseSizesInput(validBidRequest.sizes); diff --git a/modules/videoNowBidAdapter.md b/modules/videoNowBidAdapter.md deleted file mode 100644 index 2ac2a431378..00000000000 --- a/modules/videoNowBidAdapter.md +++ /dev/null @@ -1,35 +0,0 @@ -# Overview - -``` -Module Name: Videonow Bidder Adapter -Module Type: Bidder Adapter -Maintainer: info@videonow.ru -``` - -# Description - -Connect to Videonow for bids. - -The Videonow bidder adapter requires setup and approval from the videoNow team. -Please reach out to your account team or info@videonow.ru for more information. - -# Test Parameters -```javascript -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[640, 480], [300, 250], [336, 280]] - } - }, - bids: [{ - bidder: 'videonow', - params: { - pId: 1, - placementId: '36891' - } - }] - }] -``` diff --git a/modules/videobyteBidAdapter.js b/modules/videobyteBidAdapter.js index 6e99b5bc42a..2ecc3d481aa 100644 --- a/modules/videobyteBidAdapter.js +++ b/modules/videobyteBidAdapter.js @@ -221,9 +221,9 @@ function buildRequestData(bidRequest, bidderRequest) { } ], site: { - domain: window.location.hostname, - page: window.location.href, - ref: bidRequest.refererInfo ? bidRequest.refererInfo.referer || null : null + domain: bidderRequest.refererInfo.domain, + page: bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref, }, ext: { hb: 1, diff --git a/modules/videofyBidAdapter.md b/modules/videofyBidAdapter.md deleted file mode 100644 index b50eaf5672e..00000000000 --- a/modules/videofyBidAdapter.md +++ /dev/null @@ -1,36 +0,0 @@ -# Overview - -``` -Module Name: Videofy Bidder Adapter -Module Type: Bidder Adapter -Maintainer: support1@videofy.ai -``` - -# Description - -Connects to Videofy for bids. - -Videofy bid adapter supports Video ads currently. - -# Sample Ad Unit: For Publishers -```javascript -var videoAdUnit = [ -{ - code: 'video1', - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'outstream' - }, - }, - bids: [{ - bidder: 'videofy', - params: { - AV_PUBLISHERID: '55b78633181f4603178b4568', - AV_CHANNELID: '5d19dfca4b6236688c0a2fc4' - } - }] -}]; -``` - -``` diff --git a/modules/videoheroesBidAdapter.js b/modules/videoheroesBidAdapter.js new file mode 100644 index 00000000000..61123820cf5 --- /dev/null +++ b/modules/videoheroesBidAdapter.js @@ -0,0 +1,251 @@ +import { isEmpty, parseUrl, 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'; + +const BIDDER_CODE = 'videoheroes'; +const DEFAULT_CUR = 'USD'; +const ENDPOINT_URL = `https://point.contextualadv.com/?t=2&partner=hash`; + +const NATIVE_ASSETS_IDS = { 1: 'title', 2: 'icon', 3: 'image', 4: 'body', 5: 'sponsoredBy', 6: 'cta' }; +const NATIVE_ASSETS = { + title: { id: 1, name: 'title' }, + icon: { id: 2, type: 1, name: 'img' }, + image: { id: 3, type: 3, name: 'img' }, + body: { id: 4, type: 2, name: 'data' }, + sponsoredBy: { id: 5, type: 1, name: 'data' }, + cta: { id: 6, type: 12, name: 'data' } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** + * 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: (bid) => { + return !!(bid.params.placementId && bid.params.placementId.toString().length === 32); + }, + + /** + * 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: (validBidRequests, bidderRequest) => { + if (validBidRequests.length === 0 || !bidderRequest) return []; + + const endpointURL = ENDPOINT_URL.replace('hash', validBidRequests[0].params.placementId); + + let imp = validBidRequests.map(br => { + let impObject = { + id: br.bidId, + secure: 1 + }; + + if (br.mediaTypes.banner) { + impObject.banner = createBannerRequest(br); + } else if (br.mediaTypes.video) { + impObject.video = createVideoRequest(br); + } else if (br.mediaTypes.native) { + impObject.native = { + id: br.transactionId, + ver: '1.2', + request: createNativeRequest(br) + }; + } + return impObject; + }); + + let page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + + let data = { + id: bidderRequest.bidderRequestId, + cur: [ DEFAULT_CUR ], + device: { + w: screen.width, + h: screen.height, + language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', + ua: navigator.userAgent, + }, + site: { + domain: parseUrl(page).hostname, + page: page, + }, + tmax: bidderRequest.timeout || config.getConfig('bidderTimeout') || 500, + imp + }; + + if (bidderRequest.refererInfo.ref) { + data.site.ref = bidderRequest.refererInfo.ref + } + + if (bidderRequest.gdprConsent) { + data['regs'] = {'ext': {'gdpr': bidderRequest.gdprConsent.gdprApplies ? 1 : 0}}; + data['user'] = {'ext': {'consent': bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : ''}}; + } + + if (bidderRequest.uspConsent !== undefined) { + if (!data['regs'])data['regs'] = {'ext': {}}; + data['regs']['ext']['us_privacy'] = bidderRequest.uspConsent; + } + + if (config.getConfig('coppa') === true) { + if (!data['regs'])data['regs'] = {'coppa': 1}; + else data['regs']['coppa'] = 1; + } + + if (validBidRequests[0].schain) { + data['source'] = {'ext': {'schain': validBidRequests[0].schain}}; + } + + return { + method: 'POST', + url: endpointURL, + data: data + }; + }, + + /** + * 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: (serverResponse) => { + if (!serverResponse || isEmpty(serverResponse.body)) return []; + + let bids = []; + serverResponse.body.seatbid.forEach(response => { + response.bid.forEach(bid => { + let mediaType = bid.ext && bid.ext.mediaType ? bid.ext.mediaType : 'banner'; + + let bidObj = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: 1200, + currency: DEFAULT_CUR, + netRevenue: true, + creativeId: bid.crid, + dealId: bid.dealid || null, + mediaType: mediaType + }; + + switch (mediaType) { + case 'video': + bidObj.vastUrl = bid.adm; + break; + case 'native': + bidObj.native = parseNative(bid.adm); + break; + default: + bidObj.ad = bid.adm; + } + + bids.push(bidObj); + }); + }); + + return bids; + }, + + onBidWon: (bid) => { + if (isStr(bid.nurl) && bid.nurl !== '') { + triggerPixel(bid.nurl); + } + } +}; + +const parseNative = adm => { + let bid = { + clickUrl: adm.native.link && adm.native.link.url, + impressionTrackers: adm.native.imptrackers || [], + clickTrackers: (adm.native.link && adm.native.link.clicktrackers) || [], + jstracker: adm.native.jstracker || [] + }; + adm.native.assets.forEach(asset => { + let kind = NATIVE_ASSETS_IDS[asset.id]; + let content = kind && asset[NATIVE_ASSETS[kind].name]; + if (content) { + bid[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + } + }); + + return bid; +} + +const createNativeRequest = br => { + let impObject = { + ver: '1.2', + assets: [] + }; + + let keys = Object.keys(br.mediaTypes.native); + + for (let key of keys) { + const props = NATIVE_ASSETS[key]; + if (props) { + const asset = { + required: br.mediaTypes.native[key].required ? 1 : 0, + id: props.id, + [props.name]: {} + }; + + if (props.type) asset[props.name]['type'] = props.type; + if (br.mediaTypes.native[key].len) asset[props.name]['len'] = br.mediaTypes.native[key].len; + if (br.mediaTypes.native[key].sizes && br.mediaTypes.native[key].sizes[0]) { + asset[props.name]['w'] = br.mediaTypes.native[key].sizes[0]; + asset[props.name]['h'] = br.mediaTypes.native[key].sizes[1]; + } + + impObject.assets.push(asset); + } + } + + return impObject; +} + +const createBannerRequest = br => { + let size = []; + + if (br.mediaTypes.banner.sizes && Array.isArray(br.mediaTypes.banner.sizes)) { + if (Array.isArray(br.mediaTypes.banner.sizes[0])) { size = br.mediaTypes.banner.sizes[0]; } else { size = br.mediaTypes.banner.sizes; } + } else size = [300, 250]; + + return { id: br.transactionId, w: size[0], h: size[1] }; +}; + +const createVideoRequest = br => { + let videoObj = {id: br.transactionId}; + let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'minbitrate', 'maxbitrate', 'api', 'linearity']; + + for (let param of supportParamsList) { + if (br.mediaTypes.video[param] !== undefined) { + videoObj[param] = br.mediaTypes.video[param]; + } + } + + if (br.mediaTypes.video.playerSize && Array.isArray(br.mediaTypes.video.playerSize)) { + if (Array.isArray(br.mediaTypes.video.playerSize[0])) { + videoObj.w = br.mediaTypes.video.playerSize[0][0]; + videoObj.h = br.mediaTypes.video.playerSize[0][1]; + } else { + videoObj.w = br.mediaTypes.video.playerSize[0]; + videoObj.h = br.mediaTypes.video.playerSize[1]; + } + } else { + videoObj.w = 640; + videoObj.h = 480; + } + + return videoObj; +} + +registerBidder(spec); diff --git a/modules/videoheroesBidAdapter.md b/modules/videoheroesBidAdapter.md new file mode 100644 index 00000000000..f2a2ca9f7ba --- /dev/null +++ b/modules/videoheroesBidAdapter.md @@ -0,0 +1,134 @@ +# Overview + +``` +Module Name: Video Heroes Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@videoheroes.tv +``` + +# Description + +Module which connects to VideoHeroes SSP demand sources + +# Test Parameters + +250x300 banner test +``` +var adUnits = [{ + code: 'videoheroes-prebid', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'videoheroes', + params : { + placementId : "1a8d9c22db19906cb8a5fd4518d05f62" // test placementId, please replace after test + } + }] +}]; +``` + +native test +``` +var adUnits = [{ + code: 'videoheroes-native-prebid', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bids: [{ + bidder: 'videoheroes', + params: { + placementId : "1a8d9c22db19906cb8a5fd4518d05f62" // test placementId, please replace after test + } + }] +}]; +``` + +video test +``` +var adUnits = [{ + code: 'videoheroes-video-prebid', + mediaTypes: { + video: { + minduration:1, + maxduration:999, + boxingallowed:1, + skip:0, + mimes:[ + 'application/javascript', + 'video/mp4' + ], + playerSize: [[768, 1024]], + protocols:[ + 2,3 + ], + linearity:1, + api:[ + 1, + 2 + ] + } + }, + bids: [{ + bidder: 'videoheroes', + params: { + placementId : "1a8d9c22db19906cb8a5fd4518d05f62" // test placementId, please replace after test + } + }] +}]; +``` + +# Bid Parameters +## Banner + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `placementId` | required | String | The placement ID from Video Heroes | "1a8d9c22db19906cb8a5fd4518d05f62" + + +# Ad Unit and page Setup: + +```html + + + +``` diff --git a/modules/videoreachBidAdapter.js b/modules/videoreachBidAdapter.js index 61ecd55a2ef..1fc38066407 100644 --- a/modules/videoreachBidAdapter.js +++ b/modules/videoreachBidAdapter.js @@ -28,7 +28,8 @@ export const spec = { }; if (bidderRequest && bidderRequest.refererInfo) { - data.referrer = bidderRequest.refererInfo.referer; + // TODO: is 'page' the right value here? + data.referrer = bidderRequest.refererInfo.page; } if (bidderRequest && bidderRequest.gdprConsent) { diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index d268f7a9d64..ebecb5a46ae 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -12,13 +12,9 @@ const GVLID = 380; const COOKIE_SYNC_FALLBACK_URLS = [ 'https://x.bidswitch.net/sync?ssp=vidoomy', 'https://ib.adnxs.com/getuid?https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID', - 'https://pixel-sync.sitescout.com/dmp/pixelSync?nid=120&redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DCEN%26uid%3D%7BuserId%7D', - 'https://sync.1rx.io/usersync2/vidoomy?redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DUN%26uid%3D%5BRX_UUID%5D', - 'https://rtb.openx.net/sync/prebid?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&r=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dopenx%26uid%3D$%7BUID%7D', - 'https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&us_privacy=&predirect=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D', + 'https://pixel-sync.sitescout.com/dmp/pixelSync?nid=120&gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}&redir=https%3A%2F%2Fa.vidoomy.com%2Fapi%2Frtbserver%2Fcookie%3Fi%3DCEN%26uid%3D%7BuserId%7D', 'https://cm.adform.net/cookie?redirect_url=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID', - 'https://ups.analytics.yahoo.com/ups/58531/occ?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}', - 'https://ap.lijit.com/pixel?redir=https%3A%2F%2Fa-prebid.vidoomy.com%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{GDPR}}%26gdpr_consent%3D{{GDPR_CONSENT}}%26uid%3D%24UID' + 'https://ups.analytics.yahoo.com/ups/58531/occ?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}' ]; const isBidRequestValid = bid => { @@ -77,9 +73,8 @@ const buildRequests = (validBidRequests, bidderRequest) => { } const [w, h] = (parseSizesInput(sizes)[0] || '0x0').split('x'); - const aElement = document.createElement('a'); - aElement.href = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || top.location.href; - const hostname = aElement.hostname + // TODO: is 'domain' the right value here? + const hostname = bidderRequest.refererInfo.domain || window.location.hostname; const videoContext = deepAccess(bid, 'mediaTypes.video.context'); const bidfloor = deepAccess(bid, `params.bidfloor`, 0); @@ -99,7 +94,8 @@ const buildRequests = (validBidRequests, bidderRequest) => { schain: bid.schain || '', bidfloor, d: getDomainWithoutSubdomain(hostname), // 'vidoomy.com', - sp: encodeURIComponent(aElement.href), + // TODO: does the fallback make sense here? + sp: encodeURIComponent(bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation), usp: bidderRequest.uspConsent || '', coppa: !!config.getConfig('coppa'), videoContext: videoContext || '' 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..cec48a83c35 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) { 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..88a5c8c8525 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 + } } }; 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 0885df02f05..35440849db4 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -7,57 +7,94 @@ * @requires module:modules/realTimeData */ +/** + * @typedef dataCallbackMetadata + * @property {Boolean} user if true it is user-centric data + * @property {String} source describe the source of data, if "contextual" or "wam" + * @property {Boolean} isDefault if true it the default profile defined in the configuration + */ + /** onData callback type * @callback dataCallback * @param {Object} data profile data - * @param {Boolean} site true if site, else it is user + * @param {dataCallbackMetadata} meta metadata * @returns {void} */ +/** setPrebidTargeting callback type + * @callback setPrebidTargetingCallback + * @param {String} adUnitCode + * @param {Object} data + * @param {dataCallbackMetadata} metadata + * @returns {Boolean} + */ + +/** sendToBidders callback type + * @callback sendToBiddersCallback + * @param {Object} bid + * @param {String} adUnitCode + * @param {Object} data + * @param {dataCallbackMetadata} metadata + * @returns {Boolean} + */ + /** * @typedef {Object} ModuleParams - * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default undefined) - * @property {?Boolean} sendToBidders if true, will send the contextual profile to all bidders (default undefined) + * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) * @property {?dataCallback} onData callback - * @property {?WeboCtxConf} weboCtxConf - * @property {?WeboUserDataConf} weboUserDataConf + * @property {?WeboCtxConf} weboCtxConf site-centric contextual configuration + * @property {?WeboUserDataConf} weboUserDataConf user-centric wam configuration + * @property {?SfbxLiteDataConf} sfbxLiteDataConf site-centric lite configuration */ /** * @typedef {Object} WeboCtxConf * @property {string} token required token to be used on bigsea contextual API requests * @property {?string} targetURL specify the target url instead use the referer - * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default params.setPrebidTargeting or true) - * @property {?Boolean} sendToBidders if true, will send the contextual profile to all bidders (default params.sendToBidders or true) + * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) * @property {?dataCallback} onData callback * @property {?object} defaultProfile to be used if the profile is not found * @property {?Boolean} enabled if false, will ignore this configuration + * @property {?string} baseURLProfileAPI to be used to point to a different domain than ctx.weborama.com */ /** * @typedef {Object} WeboUserDataConf * @property {?number} accountId wam account id - * @property {?Boolean} setPrebidTargeting if true, will set the GAM targeting (default params.setPrebidTargeting or true) - * @property {?Boolean} sendToBidders if true, will send the user-centric profile to all bidders (default params.sendToBidders or true) + * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) * @property {?object} defaultProfile to be used if the profile is not found * @property {?dataCallback} onData callback * @property {?string} localStorageProfileKey can be used to customize the local storage key (default is 'webo_wam2gam_entry') * @property {?Boolean} enabled if false, will ignore this configuration */ +/** + * @typedef {Object} SfbxLiteDataConf + * @property {?setPrebidTargetingCallback|?Boolean|?Object} setPrebidTargeting if true, will set the GAM targeting (default undefined) + * @property {?sendToBiddersCallback|?Boolean|?Object} sendToBidders if true, will send the contextual profile to all bidders, else expects a list of allowed bidders (default undefined) + * @property {?object} defaultProfile to be used if the profile is not found + * @property {?dataCallback} onData callback + * @property {?string} localStorageProfileKey can be used to customize the local storage key (default is '_lite') + * @property {?Boolean} enabled if false, will ignore this configuration + */ import { getGlobal } from '../src/prebidGlobal.js'; import { deepSetValue, - deepAccess, isEmpty, - mergeDeep, + isFn, logError, - logWarn, - tryAppendQueryString, logMessage, - isFn + isArray, + isStr, + isBoolean, + isPlainObject, + deepClone, + tryAppendQueryString, mergeDeep, logWarn } from '../src/utils.js'; import { submodule @@ -68,21 +105,36 @@ import { import { getStorageManager } from '../src/storageManager.js'; - -const adapterManager = require('../src/adapterManager.js').default; +import adapterManager from '../src/adapterManager.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; /** @type {string} */ const SUBMODULE_NAME = 'weborama'; /** @type {string} */ +const BASE_URL_CONTEXTUAL_PROFILE_API = 'ctx.weborama.com'; +/** @type {string} */ export const DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY = 'webo_wam2gam_entry'; /** @type {string} */ const LOCAL_STORAGE_USER_TARGETING_SECTION = 'targeting'; +/** @type {string} */ +export const DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY = '_lite'; +/** @type {string} */ +const LOCAL_STORAGE_LITE_TARGETING_SECTION = 'webo'; +/** @type {string} */ +const WEBO_CTX_CONF_SECTION = 'weboCtxConf'; +/** @type {string} */ +const WEBO_USER_DATA_CONF_SECTION = 'weboUserDataConf'; +/** @type {string} */ +const SFBX_LITE_DATA_CONF_SECTION = 'sfbxLiteDataConf'; + /** @type {number} */ const GVLID = 284; -/** @type {object} */ -export const storage = getStorageManager({gvlid: GVLID, moduleName: SUBMODULE_NAME}); +/** @type {?Object} */ +export const storage = getStorageManager({ + gvlid: GVLID, + moduleName: SUBMODULE_NAME +}); /** @type {null|Object} */ let _weboContextualProfile = null; @@ -90,78 +142,67 @@ let _weboContextualProfile = null; /** @type {Boolean} */ let _weboCtxInitialized = false; -/** @type {null|Object} */ +/** @type {?Object} */ let _weboUserDataUserProfile = null; /** @type {Boolean} */ let _weboUserDataInitialized = false; +/** @type {?Object} */ +let _sfbxLiteDataProfile = null; + +/** @type {Boolean} */ +let _sfbxLiteDataInitialized = false; + /** Initialize module * @param {object} moduleConfig * @return {Boolean} true if module was initialized with success */ function init(moduleConfig) { - moduleConfig = moduleConfig || {}; - const moduleParams = moduleConfig.params || {}; - const weboCtxConf = moduleParams.weboCtxConf; - const weboUserDataConf = moduleParams.weboUserDataConf; - - _weboCtxInitialized = initWeboCtx(moduleParams, weboCtxConf); - _weboUserDataInitialized = initWeboUserData(moduleParams, weboUserDataConf); - - return _weboCtxInitialized || _weboUserDataInitialized; -} - -/** Initialize contextual sub module - * @param {ModuleParams} moduleParams - * @param {WeboCtxConf} weboCtxConf - * @return {Boolean} true if sub module was initialized with success - */ -function initWeboCtx(moduleParams, weboCtxConf) { - if (!weboCtxConf || weboCtxConf.enabled === false) { - moduleParams.weboCtxConf = null; + const moduleParams = moduleConfig?.params || {}; - return false - } - - normalizeConf(moduleParams, weboCtxConf); - - _weboCtxInitialized = false; _weboContextualProfile = null; + _weboUserDataUserProfile = null; + _sfbxLiteDataProfile = null; - if (!weboCtxConf.token) { - logWarn('missing param "token" for weborama contextual sub module initialization'); - return false; - } - - logMessage('weborama contextual intialized with success'); + _weboCtxInitialized = initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, 'token'); + _weboUserDataInitialized = initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION); + _sfbxLiteDataInitialized = initSubSection(moduleParams, SFBX_LITE_DATA_CONF_SECTION); - return true; + return _weboCtxInitialized || _weboUserDataInitialized || _sfbxLiteDataInitialized; } -/** Initialize weboUserData sub module - * @param {ModuleParams} moduleParams - * @param {WeboUserDataConf} weboUserDataConf - * @return {Boolean} true if sub module was initialized with success +/** Initialize subsection module + * @param {Object} moduleParams + * @param {string} subSection subsection name to initialize + * @param {[]string} requiredFields + * @return {Boolean} true if module subsection was initialized with success */ -function initWeboUserData(moduleParams, weboUserDataConf) { - if (!weboUserDataConf || weboUserDataConf.enabled === false) { - moduleParams.weboUserDataConf = null; +function initSubSection(moduleParams, subSection, ...requiredFields) { + const weboSectionConf = moduleParams[subSection] || {enabled: false}; + + if (weboSectionConf.enabled === false) { + delete moduleParams[subSection]; return false; } - normalizeConf(moduleParams, weboUserDataConf); + requiredFields ||= []; - _weboUserDataInitialized = false; - _weboUserDataUserProfile = null; + try { + normalizeConf(moduleParams, weboSectionConf); - let message = 'weborama user-centric intialized with success'; - if (weboUserDataConf.hasOwnProperty('accountId')) { - message = `weborama user-centric intialized with success for account: ${weboUserDataConf.accountId}`; + requiredFields.forEach(field => { + if (!weboSectionConf[field]) { + throw `missing required field "{field}" on {section}`; + } + }); + } catch (e) { + logError(`unable to initialize: error on ${subSection} configuration: ${e}`); + return false } - logMessage(message); + logMessage(`weborama ${subSection} initialized with success`); return true; } @@ -170,21 +211,172 @@ function initWeboUserData(moduleParams, weboUserDataConf) { const globalDefaults = { setPrebidTargeting: true, sendToBidders: true, - onData: (data, kind, def) => logMessage('onData(data,kind,default)', data, kind, def), + onData: () => { + /* do nothing */ }, } /** normalize submodule configuration * @param {ModuleParams} moduleParams - * @param {WeboCtxConf|WeboUserDataConf} submoduleParams + * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams * @return {void} */ function normalizeConf(moduleParams, submoduleParams) { + // handle defaults Object.entries(globalDefaults).forEach(([propertyName, globalDefaultValue]) => { if (!submoduleParams.hasOwnProperty(propertyName)) { const hasModuleParam = moduleParams.hasOwnProperty(propertyName); submoduleParams[propertyName] = (hasModuleParam) ? moduleParams[propertyName] : globalDefaultValue; } }) + + // handle setPrebidTargeting + coerceSetPrebidTargeting(submoduleParams) + + // handle sendToBidders + coerceSendToBidders(submoduleParams) + + if (!isFn(submoduleParams.onData)) { + throw 'onData parameter should be a callback'; + } + + submoduleParams.defaultProfile = submoduleParams.defaultProfile || {}; + + if (!isValidProfile(submoduleParams.defaultProfile)) { + throw 'defaultProfile is not valid'; + } +} + +/** coerce set prebid targeting to function + * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams + * @return {void} + */ +function coerceSetPrebidTargeting(submoduleParams) { + const setPrebidTargeting = submoduleParams.setPrebidTargeting; + + if (isFn(setPrebidTargeting)) { + return + } + + if (isBoolean(setPrebidTargeting)) { + const shouldSetPrebidTargeting = setPrebidTargeting; + + submoduleParams.setPrebidTargeting = () => shouldSetPrebidTargeting; + + return + } + + if (isStr(setPrebidTargeting)) { + const allowedAdUnitCode = setPrebidTargeting; + + submoduleParams.setPrebidTargeting = (adUnitCode) => allowedAdUnitCode == adUnitCode; + + return + } + + if (isArray(setPrebidTargeting)) { + const allowedAdUnitCodes = setPrebidTargeting; + + submoduleParams.setPrebidTargeting = (adUnitCode) => allowedAdUnitCodes.includes(adUnitCode); + + return + } + + throw `unexpected format for setPrebidTargeting: ${typeof setPrebidTargeting}`; +} + +/** coerce send to bidders to function + * @param {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} submoduleParams + * @return {void} + */ +function coerceSendToBidders(submoduleParams) { + const sendToBidders = submoduleParams.sendToBidders; + + if (isFn(sendToBidders)) { + return + } + + if (isBoolean(sendToBidders)) { + const shouldSendToBidders = sendToBidders; + + submoduleParams.sendToBidders = () => shouldSendToBidders; + + return + } + + if (isStr(sendToBidders)) { + const allowedBidder = sendToBidders; + + submoduleParams.sendToBidders = (bid) => allowedBidder == bid.bidder; + + return + } + + if (isArray(sendToBidders)) { + const allowedBidders = sendToBidders; + + submoduleParams.sendToBidders = (bid) => allowedBidders.includes(bid.bidder); + + return + } + + if (isPlainObject(sendToBidders)) { + const sendToBiddersMap = sendToBidders; + submoduleParams.sendToBidders = (bid, adUnitCode) => { + const bidder = bid.bidder; + if (!sendToBiddersMap.hasOwnProperty(bidder)) { + return false + } + + const value = sendToBiddersMap[bidder]; + + if (isBoolean(value)) { + return value + } + + if (isStr(value)) { + return value == adUnitCode + } + + if (isArray(value)) { + return value.includes(adUnitCode) + } + + throw `unexpected format for sendToBidders[${bidder}]: ${typeof value}`; + }; + + return + } + + throw `unexpected format for sendToBidders: ${typeof sendToBidders}`; +} +/** + * check if profile is valid + * @param {*} profile + * @returns {Boolean} + */ +function isValidProfile(profile) { + if (!isPlainObject(profile)) { + return false; + } + + const keys = Object.keys(profile); + + for (var i in keys) { + const key = keys[i]; + const value = profile[key]; + if (!isArray(value)) { + return false; + } + + for (var j in value) { + const elem = value[j] + if (!isStr(elem)) { + return false; + } + } + } + + return true; } /** function that provides ad server targeting data to RTD-core @@ -193,24 +385,27 @@ function normalizeConf(moduleParams, submoduleParams) { * @returns {Object} target data */ function getTargetingData(adUnitsCodes, moduleConfig) { - moduleConfig = moduleConfig || {}; - const moduleParams = moduleConfig.params || {}; - const weboCtxConf = moduleParams.weboCtxConf || {}; - const weboUserDataConf = moduleParams.weboUserDataConf || {}; - const weboCtxConfTargeting = weboCtxConf.setPrebidTargeting; - const weboUserDataConfTargeting = weboUserDataConf.setPrebidTargeting; + const moduleParams = moduleConfig?.params || {}; - try { - const profile = getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting); + const profileHandlers = buildProfileHandlers(moduleParams); - if (isEmpty(profile)) { - return {}; - } + if (isEmpty(profileHandlers)) { + logMessage('no data to set targeting'); + return {}; + } + try { const td = adUnitsCodes.reduce((data, adUnitCode) => { - if (adUnitCode) { - data[adUnitCode] = profile; - } + data[adUnitCode] = profileHandlers.reduce((targeting, ph) => { + // logMessage(`check if should set targeting for adunit '${adUnitCode}'`); + const cph = copyProfileHandler(ph); + if (ph.setTargeting(adUnitCode, cph.data, cph.metadata)) { + // logMessage(`set targeting for adunit '${adUnitCode}', source '${ph.metadata.source}'`); + + mergeDeep(targeting, cph.data); + } + return targeting; + }, {}); return data; }, {}); @@ -221,57 +416,155 @@ function getTargetingData(adUnitsCodes, moduleConfig) { } } -/** function that provides complete profile formatted to be used +/** function that provides data handlers based on the configuration * @param {ModuleParams} moduleParams - * @param {Boolean} weboCtxConfTargeting - * @param {Boolean} weboUserDataConfTargeting - * @returns {Object} complete profile + * @returns {Array} handlers */ -function getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting) { - const profile = {}; +function buildProfileHandlers(moduleParams) { + const profileHandlers = []; + + if (_weboCtxInitialized && moduleParams?.weboCtxConf) { + const weboCtxConf = moduleParams.weboCtxConf; + const [data, isDefault] = getContextualProfile(weboCtxConf); + if (!isEmpty(data)) { + profileHandlers.push({ + data: data, + metadata: { + user: false, + source: 'contextual', + isDefault: !!isDefault, + }, + setTargeting: weboCtxConf.setPrebidTargeting, + sendToBidders: weboCtxConf.sendToBidders, + onData: weboCtxConf.onData, + }) + } else { + logMessage('skip contextual profile: no data'); + } + } - if (weboCtxConfTargeting) { - const contextualProfile = getContextualProfile(moduleParams.weboCtxConf || {}); - mergeDeep(profile, contextualProfile); + if (_weboUserDataInitialized && moduleParams?.weboUserDataConf) { + const weboUserDataConf = moduleParams.weboUserDataConf; + const [data, isDefault] = getWeboUserDataProfile(weboUserDataConf); + if (!isEmpty(data)) { + profileHandlers.push({ + data: data, + metadata: { + user: true, + source: 'wam', + isDefault: !!isDefault, + }, + setTargeting: weboUserDataConf.setPrebidTargeting, + sendToBidders: weboUserDataConf.sendToBidders, + onData: weboUserDataConf.onData, + }) + } else { + logMessage('skip wam profile: no data'); + } } - if (weboUserDataConfTargeting) { - const weboUserDataProfile = getWeboUserDataProfile(moduleParams.weboUserDataConf || {}); - mergeDeep(profile, weboUserDataProfile); + if (_sfbxLiteDataInitialized && moduleParams?.sfbxLiteDataConf) { + const sfbxLiteDataConf = moduleParams.sfbxLiteDataConf; + const [data, isDefault] = getSfbxLiteDataProfile(sfbxLiteDataConf); + if (!isEmpty(data)) { + profileHandlers.push({ + data: data, + metadata: { + user: false, + source: 'lite', + isDefault: !!isDefault, + }, + setTargeting: sfbxLiteDataConf.setPrebidTargeting, + sendToBidders: sfbxLiteDataConf.sendToBidders, + onData: sfbxLiteDataConf.onData, + }) + } else { + logMessage('skip sfbx lite profile: no data'); + } } - return profile; + return profileHandlers; } /** return contextual profile * @param {WeboCtxConf} weboCtxConf - * @returns {Object} contextual profile + * @returns {Array} contextual profile + isDefault boolean flag */ function getContextualProfile(weboCtxConf) { + if (_weboContextualProfile) { + return [_weboContextualProfile, false]; + } + const defaultContextualProfile = weboCtxConf.defaultProfile || {}; - return _weboContextualProfile || defaultContextualProfile; + + return [defaultContextualProfile, true]; } /** return weboUserData profile * @param {WeboUserDataConf} weboUserDataConf - * @returns {Object} weboUserData profile + * @returns {Array} weboUserData profile + isDefault boolean flag */ function getWeboUserDataProfile(weboUserDataConf) { - const weboUserDataDefaultUserProfile = weboUserDataConf.defaultProfile || {}; + return getDataFromLocalStorage(weboUserDataConf, + () => _weboUserDataUserProfile, + (data) => _weboUserDataUserProfile = data, + DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY, + LOCAL_STORAGE_USER_TARGETING_SECTION, + 'wam'); +} + +/** return weboUserData profile + * @param {SfbxLiteDataConf} sfbxLiteDataConf + * @returns {Array} sfbxLiteData profile + isDefault boolean flag + */ +function getSfbxLiteDataProfile(sfbxLiteDataConf) { + return getDataFromLocalStorage(sfbxLiteDataConf, + () => _sfbxLiteDataProfile, + (data) => _sfbxLiteDataProfile = data, + DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY, + LOCAL_STORAGE_LITE_TARGETING_SECTION, + 'lite'); +} + +/** return generic webo data profile + * @param {WeboUserDataConf|SfbxLiteDataConf} weboDataConf + * @param {cacheGetCallback} cacheGet + * @param {cacheSetCallback} cacheSet + * @param {String} defaultLocalStorageProfileKey + * @param {String} targetingSection + * @param {String} source + * @returns {Array} webo (user|lite) data profile + isDefault boolean flag + */ +function getDataFromLocalStorage(weboDataConf, cacheGet, cacheSet, defaultLocalStorageProfileKey, targetingSection, source) { + const defaultProfile = weboDataConf.defaultProfile || {}; - if (storage.localStorageIsEnabled() && !_weboUserDataUserProfile) { - const localStorageProfileKey = weboUserDataConf.localStorageProfileKey || DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY; + if (storage.localStorageIsEnabled() && !cacheGet()) { + const localStorageProfileKey = weboDataConf.localStorageProfileKey || defaultLocalStorageProfileKey; const entry = storage.getDataFromLocalStorage(localStorageProfileKey); if (entry) { const data = JSON.parse(entry); - if (data && Object.keys(data).length > 0) { - _weboUserDataUserProfile = data[LOCAL_STORAGE_USER_TARGETING_SECTION]; + if (data && isPlainObject(data) && data.hasOwnProperty(targetingSection)) { + const profile = data[targetingSection]; + const valid = isValidProfile(profile); + if (!valid) { + logWarn(`found invalid ${source} profile on local storage key ${localStorageProfileKey}, section ${targetingSection}`); + } + + if (valid && !isEmpty(data)) { + cacheSet(profile); + } } } } - return _weboUserDataUserProfile || weboUserDataDefaultUserProfile; + const profile = cacheGet() + + if (profile) { + return [profile, false]; + } + + return [defaultProfile, true]; } /** function that will allow RTD sub-modules to modify the AdUnit object for each auction @@ -281,102 +574,83 @@ function getWeboUserDataProfile(weboUserDataConf) { * @returns {void} */ export function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig) { - moduleConfig = moduleConfig || {}; - const moduleParams = moduleConfig.params || {}; - const weboCtxConf = moduleParams.weboCtxConf || {}; - - const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + const moduleParams = moduleConfig?.params || {}; if (!_weboCtxInitialized) { - handleBidRequestData(adUnits, moduleParams); + handleBidRequestData(reqBidsConfigObj, moduleParams); onDone(); return; } + const weboCtxConf = moduleParams.weboCtxConf || {}; + fetchContextualProfile(weboCtxConf, (data) => { logMessage('fetchContextualProfile on getBidRequestData is done'); setWeboContextualProfile(data); }, () => { - handleBidRequestData(adUnits, moduleParams); + handleBidRequestData(reqBidsConfigObj, moduleParams); onDone(); }); } /** function that handles bid request data - * @param {Object[]} adUnits + * @param {Object} reqBids * @param {ModuleParams} moduleParams * @returns {void} */ +function handleBidRequestData(reqBids, moduleParams) { + const profileHandlers = buildProfileHandlers(moduleParams); -function handleBidRequestData(adUnits, moduleParams) { - const weboCtxConf = moduleParams.weboCtxConf || {}; - const weboUserDataConf = moduleParams.weboUserDataConf || {}; - const weboCtxConfTargeting = weboCtxConf.sendToBidders; - const weboUserDataConfTargeting = weboUserDataConf.sendToBidders; - - if (weboCtxConfTargeting) { - const contextualProfile = getContextualProfile(weboCtxConf); - if (!isEmpty(contextualProfile)) { - setBidRequestProfile(adUnits, contextualProfile, true); - } - } - - if (weboUserDataConfTargeting) { - const weboUserDataProfile = getWeboUserDataProfile(weboUserDataConf); - if (!isEmpty(weboUserDataProfile)) { - setBidRequestProfile(adUnits, weboUserDataProfile, false); - } + if (isEmpty(profileHandlers)) { + logMessage('no data to send to bidders'); + return; } - handleOnData(weboCtxConf, weboUserDataConf); -} - -/** function that handle with onData callbacks - * @param {WeboCtxConf} weboCtxConf - * @param {WeboUserDataConf} weboUserDataConf - */ + const adUnits = reqBids.adUnits || getGlobal().adUnits; -function handleOnData(weboCtxConf, weboUserDataConf) { - const callbacks = [{ - onData: weboCtxConf.onData, - fetchData: () => getContextualProfile(weboCtxConf), - site: true, - }, { - onData: weboUserDataConf.onData, - fetchData: () => getWeboUserDataProfile(weboUserDataConf), - site: false, - }]; + try { + adUnits.filter( + adUnit => adUnit.hasOwnProperty('bids') + ).forEach( + adUnit => adUnit.bids.forEach( + bid => profileHandlers.forEach(ph => { + // logMessage(`check if bidder '${bid.bidder}' and adunit '${adUnit.code} are share ${ph.metadata.source} data`); + + const cph = copyProfileHandler(ph); + if (ph.sendToBidders(bid, adUnit.code, cph.data, cph.metadata)) { + // logMessage(`handling bidder '${bid.bidder}' with ${ph.metadata.source} data`); + + handleBid(reqBids, bid, cph.data, ph.metadata); + } + }) + ) + ); + } catch (e) { + logError('unable to send data to bidders:', e); + } - callbacks.filter(obj => isFn(obj.onData)).forEach(obj => { + profileHandlers.forEach(ph => { try { - const data = obj.fetchData(); - obj.onData(data, obj.site); + const cph = copyProfileHandler(ph); + ph.onData(cph.data, cph.metadata); } catch (e) { - const kind = (obj.site) ? 'site' : 'user'; - logError(`error while executure onData callback with ${kind}-based data:`, e); + logError(`error while executure onData callback with ${ph.metadata.source}-based data:`, e); } }); } - -/** function that set bid request data on each segment (site or user centric) - * @param {Object[]} adUnits - * @param {Object} profile - * @param {Boolean} site true if site centric, else it is user centric - * @returns {void} +/** function that handles bid request data + * @param {Object} ph profile handler + *@returns {Object} of deeply copy data and metadata */ -function setBidRequestProfile(adUnits, profile, site) { - setGlobalOrtb2(profile, site); - - adUnits.forEach(adUnit => { - if (adUnit.hasOwnProperty('bids')) { - const adUnitCode = adUnit.code || 'no code'; - adUnit.bids.forEach(bid => handleBid(adUnitCode, profile, site, bid)); - } - }); +function copyProfileHandler(ph) { + return { + data: deepClone(ph.data), + metadata: deepClone(ph.metadata), + }; } /** @type {string} */ @@ -395,115 +669,63 @@ const SMARTADSERVER = 'smartadserver'; const bidderAliasRegistry = adapterManager.aliasRegistry || {}; /** handle individual bid - * @param {string} adUnitCode - * @param {Object} profile - * @param {Boolean} site true if site centric, else it is user centric + * @param reqBids * @param {Object} bid + * @param {Object} profile + * @param {Object} metadata * @returns {void} */ -function handleBid(adUnitCode, profile, site, bid) { +function handleBid(reqBids, bid, profile, metadata) { const bidder = bidderAliasRegistry[bid.bidder] || bid.bidder; - logMessage(`handling on adunit '${adUnitCode}', bidder '${bidder}' and bid`, bid); - switch (bidder) { + // TODO: these special cases should not be necessary - all adapters should look into FPD, not just their params case APPNEXUS: - handleAppnexusBid(profile, bid); + handleAppnexusBid(bid, profile); break; case PUBMATIC: - handlePubmaticBid(profile, bid); + handlePubmaticBid(bid, profile); break; case SMARTADSERVER: - handleSmartadserverBid(profile, bid); + handleSmartadserverBid(bid, profile); break; case RUBICON: - handleRubiconBid(profile, site, bid); + handleRubiconBid(bid, profile, metadata); break; default: - logMessage(`unsupported bidder '${bidder}', trying via bidder ortb2 fpd`); - const section = ((site) ? 'site' : 'user'); - const base = `ortb2.${section}.ext.data`; - - assignProfileToObject(bid, base, profile); + handleBidViaORTB2(reqBids, bid, profile, metadata); } } -/** - * set ortb2 global data - * @param {Object} profile - * @param {Boolean} site - * @returns {void} - */ -function setGlobalOrtb2(profile, site) { - const section = ((site) ? 'site' : 'user'); - const base = `${section}.ext.data`; - const addOrtb2 = {}; - - assignProfileToObject(addOrtb2, base, profile); - - if (!isEmpty(addOrtb2)) { - const testGlobal = getGlobal().getConfig('ortb2') || {}; - const ortb2 = { - ortb2: mergeDeep({}, testGlobal, addOrtb2) - }; - getGlobal().setConfig(ortb2); - } -} - -/** - * assign profile to object - * @param {Object} destination - * @param {string} base - * @param {Object} profile - * @returns {void} - */ -function assignProfileToObject(destination, base, profile) { - Object.keys(profile).forEach(key => { - const path = `${base}.${key}`; - deepSetValue(destination, path, profile[key]) - }) -} - -/** handle rubicon bid - * @param {Object} profile - * @param {Boolean} site - * @param {Object} bid - * @returns {void} - */ -function handleRubiconBid(profile, site, bid) { - const section = (site) ? 'inventory' : 'visitor'; - const base = `params.${section}`; - assignProfileToObject(bid, base, profile); -} - /** handle appnexus/xandr bid - * @param {Object} profile * @param {Object} bid + * @param {Object} profile * @returns {void} */ -function handleAppnexusBid(profile, bid) { +function handleAppnexusBid(bid, profile) { const base = 'params.keywords'; assignProfileToObject(bid, base, profile); } /** handle pubmatic bid - * @param {Object} profile * @param {Object} bid + * @param {Object} profile * @returns {void} */ -function handlePubmaticBid(profile, bid) { +function handlePubmaticBid(bid, profile) { const sep = '|'; const subsep = ','; - const bidKey = 'params.dctr'; const target = []; - const data = deepAccess(bid, bidKey); + bid.params ||= {}; + + const data = bid.params.dctr; if (data) { data.split(sep).forEach(t => target.push(t)); } @@ -516,20 +738,21 @@ function handlePubmaticBid(profile, bid) { } }); - deepSetValue(bid, bidKey, target.join(sep)); + bid.params.dctr = target.join(sep); } /** handle smartadserver bid - * @param {Object} profile * @param {Object} bid + * @param {Object} profile * @returns {void} */ -function handleSmartadserverBid(profile, bid) { +function handleSmartadserverBid(bid, profile) { const sep = ';'; - const bidKey = 'params.target'; const target = []; - const data = deepAccess(bid, bidKey); + bid.params ||= {}; + + const data = bid.params.target; if (data) { data.split(sep).forEach(t => target.push(t)); } @@ -542,7 +765,57 @@ function handleSmartadserverBid(profile, bid) { } }); }); - deepSetValue(bid, bidKey, target.join(sep)); + + bid.params.target = target.join(sep); +} + +/** handle rubicon bid + * @param {Object} bid + * @param {Object} profile + * @param {Object} metadata + * @returns {void} + */ +function handleRubiconBid(bid, profile, metadata) { + if (isBoolean(metadata.user)) { + const section = (metadata.user) ? 'visitor' : 'inventory'; + const base = `params.${section}`; + assignProfileToObject(bid, base, profile); + } else { + logMessage(`SKIP bidder '${bid.bidder}', data from '${metadata.source}' is not defined as user or site-centric`); + } +} + +/** handle generic bid via ortb2 arbitrary data + * @param reqBids + * @param {Object} bid + * @param {Object} profile + * @param {Object} metadata + * @returns {void} + */ +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 = `${bid.bidder}.${section}.ext.data`; + + 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`); + } +} + +/** + * assign profile to object + * @param {Object} destination + * @param {string} base + * @param {Object} profile + * @returns {void} + */ +function assignProfileToObject(destination, base, profile) { + Object.keys(profile).forEach(key => { + const path = `${base}.${key}`; + deepSetValue(destination, path, profile[key]) + }) } /** set bigsea contextual profile on module state @@ -550,7 +823,7 @@ function handleSmartadserverBid(profile, bid) { * @returns {void} */ export function setWeboContextualProfile(data) { - if (data && Object.keys(data).length > 0) { + if (data && isPlainObject(data) && isValidProfile(data) && !isEmpty(data)) { _weboContextualProfile = data; } } @@ -575,15 +848,16 @@ export function setWeboContextualProfile(data) { function fetchContextualProfile(weboCtxConf, onSuccess, onDone) { const targetURL = weboCtxConf.targetURL || document.URL; const token = weboCtxConf.token; + const baseURLProfileAPI = weboCtxConf.baseURLProfileAPI || BASE_URL_CONTEXTUAL_PROFILE_API; let queryString = ''; queryString = tryAppendQueryString(queryString, 'token', token); queryString = tryAppendQueryString(queryString, 'url', targetURL); - const url = `https://ctx.weborama.com/api/profile?${queryString}`; + const urlProfileAPI = `https://${baseURLProfileAPI}/api/profile?${queryString}`; - ajax(url, { - success: function(response, req) { + ajax(urlProfileAPI, { + success: (response, req) => { if (req.status === 200) { try { const data = JSON.parse(response); @@ -598,7 +872,7 @@ function fetchContextualProfile(weboCtxConf, onSuccess, onDone) { onDone(); } }, - error: function() { + error: () => { onDone(); logError('unable to get weborama data'); } diff --git a/modules/weboramaRtdProvider.md b/modules/weboramaRtdProvider.md index 732944c6e1c..88ec907c9a1 100644 --- a/modules/weboramaRtdProvider.md +++ b/modules/weboramaRtdProvider.md @@ -6,11 +6,17 @@ Module Type: Rtd Provider Maintainer: prebid-support@weborama.com ``` -# Description +## Description -Weborama provides a Semantic AI Contextual API that classifies in Real-time a web page seen by a web user within generic and custom topics. It enables publishers to better monetize their inventory and unlock it to programmatic. +Weborama provides a Real-Time Data Submodule for `Prebid.js`, allowing to easy integrate different products such as: -Contact prebid-support@weborama.com for information. +* Semantic AI Contextual API that classifies in Real-time a web page seen by a web user within generic and custom topics. It enables publishers to better monetize their inventory and unlock it to programmatic. + +* Weborama Audience Manager (WAM) is a DMP (Data Management Platform) used by over 60 companies in the world. This platform distinguishes itself particularly by a high level interconnexion with the adtech & martech ecosystem and a transparent access to the database intelligence. + +* LiTE by SFBX® (Local inApp Trust Engine) provides “Zero Party Data” given by users, stored and calculated only on the user’s device. Through a unique cohorting system, it enables better monetization in a consent/consentless and identity-less mode. + +Contact prebid-support@weborama.com for more information. ### Publisher Usage @@ -18,7 +24,7 @@ Compile the Weborama RTD module into your Prebid build: `gulp build --modules=rtdModule,weboramaRtdProvider` -Add the Weborama RTD provider to your Prebid config. +Add the Weborama RTD provider to your Prebid config, use the configuration template below: ```javascript var pbjs = pbjs || {}; @@ -26,94 +32,525 @@ pbjs.que = pbjs.que || []; pbjs.que.push(function () { pbjs.setConfig({ - debug: true, + debug: true, // Output debug messages to the web console, *should* be disabled in production realTimeData: { auctionDelay: 1000, dataProviders: [{ name: "weborama", waitForIt: true, params: { - setPrebidTargeting: true, // optional - sendToBidders: true, // optional - onData: function(data, site){ // optional - var kind = (site)? 'site' : 'user'; - console.log('onData', kind, data); - }, - weboCtxConf: { - token: "to-be-defined", // mandatory - targetURL: "https://prebid.org", // default is document.URL - setPrebidTargeting: true, // override param.setPrebidTargeting or default true - sendToBidders: true, // override param.sendToBidders or default true - defaultProfile: { // optional - webo_ctx: ['moon'], - webo_ds: ['bar'] - } - //, onData: function (data, ...) { ...} - }, - weboUserDataConf: { - accountId: 12345, // optional, used for logging - setPrebidTargeting: true, // override param.setPrebidTargeting or default true - sendToBidders: true, // override param.sendToBidders or default true - defaultProfile: { // optional - webo_cs: ['Red'], - webo_audiences: ['bam'] - }, - localStorageProfileKey: 'webo_wam2gam_entry' // default - //, onData: function (data, ...) { ...} - } - } - }] + /* add weborama rtd submodule configuration here */ + }, + }, + // other modules... + ] } }); }); ``` +The module configuration has 3 independent sections (`weboCtxConf`, `weboUserDataConf` and `sfbxLiteDataConf`), each one mapped to a single product (`contextual`, `wam` and `lite`). No section is enabled by default, we must be explicit like in the minimal example below: + +```javascript +pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + weboCtxConf: { // contextual site-centric configuration, *omit if not needed* + token: "<>", // mandatory + }, + weboUserDataConf: { // wam user-centric configuration, *omit if not needed* + enabled: true, + }, + sfbxLiteDataConf: { // sfbx-lite site-centric configuration, *omit if not needed* + enabled: true, + }, + } + }, + // other modules... + ] + } +}); +``` + +Each module can perform two actions: + +* set targeting on [GPT](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForGPTAsync.html) / [AST](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForAst.html]) via `prebid.js` + +* send data to other `prebid.js` bidder modules (check the complete list at the end of this page) + ### Parameter Descriptions for the Weborama Configuration Section +This is the main configuration section + | Name |Type | Description | Notes | | :------------ | :------------ | :------------ |:------------ | | name | String | Real time data module name | Mandatory. Always 'Weborama' | | waitForIt | Boolean | Mandatory. Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false but recommended to true | | params | Object | | Optional | -| params.setPrebidTargeting | Boolean | If true, may use the profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | -| params.sendToBidders | Boolean | If true, may send the profile to all bidders | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | -| params.weboCtxConf | Object | Weborama Contextual Configuration | Optional -| params.weboUserDataConf | Object | Weborama User-Centric Configuration | Optional | -| params.onData | Callback | If set, will receive the profile and site flag | Optional. Affects the `weboCtxConf` and `weboUserDataConf` sections | +| params.setPrebidTargeting | Boolean | If true, may use the profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js | Optional. Affects the `weboCtxConf`, `weboUserDataConf` and `sfbxLiteDataConf` sections | +| params.sendToBidders | Boolean or Array | If true, may send the profile to all bidders. If an array, will specify the bidders to send data | Optional. Affects the `weboCtxConf`, `weboUserDataConf` and `sfbxLiteDataConf` sections | +| params.weboCtxConf | Object | Weborama Contextual Site-Centric Configuration | Optional | +| params.weboUserDataConf | Object | Weborama WAM User-Centric Configuration | Optional | +| params.sfbxLiteDataConf | Object | Sfbx LiTE Site-Centric Configuration | Optional | +| params.onData | Callback | If set, will receive the profile and metadata | Optional. Affects the `weboCtxConf`, `weboUserDataConf` and `sfbxLiteDataConf` sections | + +#### Contextual Site-Centric Configuration -#### Contextual Configuration +To be possible use the integration with Weborama Contextual Service you must be a client with a valid API token. Please contact weborama if you don't have it. + +On this section we will explain the `params.weboCtxConf` subconfiguration: | Name |Type | Description | Notes | | :------------ | :------------ | :------------ |:------------ | | token | String | Security Token provided by Weborama, unique per client | Mandatory | | targetURL | String | Url to be profiled in the contextual api | Optional. Defaults to `document.URL` | -| setPrebidTargeting|Boolean|If true, will use the contextual profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or **true**.| -| sendToBidders|Boolean|If true, will send the contextual profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or **true**.| +| setPrebidTargeting|Various|If true, will use the contextual profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or `true`.| +| sendToBidders|Various|If true, will send the contextual profile to all bidders. If an array, will specify the bidders to send data| Optional. Default is `params.sendToBidders` (if any) or `true`.| | defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | +| onData | Callback | If set, will receive the profile and metadata | Optional. Default is `params.onData` (if any) or log via prebid debug | +| enabled | Boolean| if false, will ignore this configuration| Default is `true` if this section is present| +| baseURLProfileAPI | String| if present, update the domain of the contextual api| Optional. Default is `ctx.weborama.com` | + +#### WAM User-Centric Configuration + +To be possible use the integration with Weborama Audience Manager (WAM) you must be a client with an account id and you lust include the `wamfactory` script in your pages with `wam2gam` feature activated. +Please contact weborama if you don't have it. + +On this section we will explain the `params.weboUserDataConf` subconfiguration: + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| accountId|Number|WAM account id. If you don't have it, please contact weborama. | Recommended.| +| setPrebidTargeting|Various|If true, will use the user profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or `true`.| +| sendToBidders|Various|If true, will send the user profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or `true`.| | onData | Callback | If set, will receive the profile and site flag | Optional. Default is `params.onData` (if any) or log via prebid debug | -| enabled | Boolean| if false, will ignore this configuration| default true| +| defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | +| localStorageProfileKey| String | can be used to customize the local storage key | Optional | +| enabled | Boolean| if false, will ignore this configuration| Default is `true` if this section is present| + +#### Sfbx LiTE Site-Centric Configuration + +To be possible use the integration between Weborama and Sfbx LiTE you should also contact SFBX® to setup this product. -#### User-Centric Configuration +On this section we will explain the `params.sfbxLiteDataConf` subconfiguration: | Name |Type | Description | Notes | | :------------ | :------------ | :------------ |:------------ | -| accountId|Number|WAM account id. If present, will be used on logging and statistics| Optional.| -| setPrebidTargeting|Boolean|If true, will use the user profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or **true**.| -| sendToBidders|Boolean|If true, will send the user profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or **true**.| +| setPrebidTargeting|Various|If true, will use the user profile to set the prebid (GPT/GAM or AST) targeting of all adunits managed by prebid.js| Optional. Default is `params.setPrebidTargeting` (if any) or `true`.| +| sendToBidders|Varios|If true, will send the user profile to all bidders| Optional. Default is `params.sendToBidders` (if any) or `true`.| | onData | Callback | If set, will receive the profile and site flag | Optional. Default is `params.onData` (if any) or log via prebid debug | | defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | -| localStorageProfileKey| String | can be used to customize the local storage key | Optional | -| enabled | Boolean| if false, will ignore this configuration| default true| +| localStorageProfileKey| String | can be used to customize the local storage key | Optional | +| enabled | Boolean| if false, will ignore this configuration| Default is `true` if this section is present| + +##### Property setPrebidTargeting supported types + +This property support the following types + +| Type | Description | Example | Notes | +| :------------ | :------------ | :------------ |:------------ | +| Boolean|If true, set prebid targeting for all adunits, or not in case of false| `true` | default value | +| String|Will set prebid targeting only for one adunit | `'adUnitCode1'` | | +| Array of Strings|Will set prebid targeting only for some adunits| `['adUnitCode1','adUnitCode2']` | | +| Callback |Will be executed for each adunit, expects return a true value to set prebid targeting or not| `function(adUnitCode){return adUnitCode == 'adUnitCode';}` | | + +The complete callback function signature is: + +```javascript +setPrebidTargeting: function(adUnitCode, data, metadata){ + return true; // or false, depending on the logic +} +``` + +This callback will be executed with the adUnitCode, profile and a metadata with the following fields + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| user | Boolean | If true, it contains user-centric data | | +| source | String | Represent the source of data | can be `contextual`, `wam` or `lite` | +| isDefault | Boolean | If true, it contains the default profile defined in the configuration | | + +It is possible customize the targeting based on the parameters: + +```javascript +setPrebidTargeting: function(adUnitCode, data, metadata){ + // check metadata.source can be omitted if defined in params.weboUserDataConf + if (adUnitCode == 'adUnitCode1' && metadata.source == 'wam'){ + data['foo']=['bar']; // add this section only for adUnitCode1 + delete data['other']; // remove this section + } + return true; +} +``` + +##### Property sendToBidders supported types + +This property support the following types + +| Type | Description | Example | Notes | +| :------------ | :------------ | :------------ |:------------ | +| Boolean|If true, send data to all bidders, or not in case of false| `true` | default value | +| String|Will send data to only one bidder | `'appnexus'` | | +| Array of Strings|Will send data to only some bidders | `['appnexus','pubmatic']` | | +| Object |Will send data to only some bidders and some ad units | `{appnexus: true, pubmatic:['adUnitCode1']}` | | +| Callback |Will be executed for each adunit, expects return a true value to set prebid targeting or not| `function(bid, adUnitCode){return bid.bidder == 'appnexus' && adUnitCode == 'adUnitCode';}` | | + +A better look on the `Object` type + +```javascript +sendToBidders: { + appnexus: true, // send profile to appnexus on all ad units + pubmatic: ['adUnitCode1'],// send profile to pubmatic on this ad units +} +``` + +The complete callback function signature is: + +```javascript +sendToBidders: function(bid, adUnitCode, data, metadata){ + return true; // or false, depending on the logic +} +``` + +This callback will be executed with the bid object (contains a field `bidder` with name), adUnitCode, profile and a metadata with the following fields + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| user | Boolean | If true, it contains user-centric data | | +| source | String | Represent the source of data | can be `contextual`, `wam` or `lite` | +| isDefault | Boolean | If true, it contains the default profile defined in the configuration | | + +It is possible customize the targeting based on the parameters: + +```javascript +sendToBidders: function(bid, adUnitCode, data, metadata){ + if (bid.bidder == 'appnexus' && adUnitCode == 'adUnitCode1'){ + data['foo']=['bar']; // add this section only for appnexus + adUnitCode1 + delete data['other']; // remove this section + } + return true; +} +``` + +To be possible customize the way we send data to bidders via this callback: + +```javascript +sendToBidders: function(bid, adUnitCode, data, metadata){ + if (bid.bidder == 'other'){ + /* use bid object to store data based on this specific logic, like in the example below */ + + bid.params = bid.params || {}; + bid.params['some_specific_key'] = data; + + return false; // will prevent the module to follow the pre-defined logic per bidder + } + // others + return true; +} +``` + +In case of using bid _aliases_, we should match the same string used in the adUnit configuration. + +```javascript +pbjs.aliasBidder('appnexus', 'foo'); +pbjs.aliasBidder('criteo', 'bar'); +pbjs.aliasBidder('pubmatic', 'baz'); +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + weboCtxConf: { + token: "to-be-defined", // mandatory + sendToBidders: ['foo','bar'], // will share site-centric data with bidders foo and bar + }, + weboUserDataConf: { + accountId: 12345, // recommended, + sendToBidders: ['baz'], // will share user-centric data with only bidder baz + } + } + }] + } +}); +``` + +##### Using onData callback + +We can specify a callback to handle the profile data from site-centric or user-centric data. + +This callback will be executed with the profile and a metadata with the following fields + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| user | Boolean | If true, it contains user-centric data | | +| source | String | Represent the source of data | can be `contextual`, `wam` or `lite` | +| isDefault | Boolean | If true, it contains the default profile defined in the configuration | | + +The metadata maybe not useful if we define the callback on site-centric of user-centric configuration, but if defined in the global level: + +```javascript +params: { + onData: function(data, metadata){ + var hasUserCentricData = metadata.user; + var dataSource = metadata.source; + console.log('onData', data, hasUserCentricData, dataSource); + } +} +``` + +an interesting example is to set GAM targeting in global level instead in slot level only for contextual data: + +```javascript +params: { + weboCtxConf: { + token: 'to-be-defined', + setPrebidTargeting: false, + onData: function(data, metadata){ + var googletag = googletag || {}; + googletag.cmd = googletag.cmd || []; + googletag.cmd.push(function () { + for(var key in data){ + googletag.pubads().setTargeting(key, data[key]); + } + }); + }, + } +} +``` + +### More configuration examples + +A more complete example can be found below. We can define default profiles, for each section, to be used in case of no data are found. + +We can control if we will set prebid targeting or send data to bidders in a global level or on each section (`contextual`, `wam` or `lite`). + +By default we try to send the data to all destinations, always. To restrict we can have two choices: + +* Set `setPrebidTargeting` or `sendToBidders` explicity to `true` or `false` on each section; +* Set `setPrebidTargeting` or `sendToBidders` globally to `false` and only enable on the right sections; + +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function () { + pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + weboCtxConf: { + token: "<>", // mandatory + targetURL: "https://example.org", // default is document.URL + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + sendToBidders: true, // override param.sendToBidders. default is true + defaultProfile: { // optional, used if nothing is found + webo_ctx: [ ... ], // contextual segments + webo_ds: [ ...], // data science segments + }, + enabled: true, + }, + weboUserDataConf: { + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + sendToBidders: true, // override param.sendToBidders. default is true + defaultProfile: { // optional, used if nothing is found + webo_cs: [...], // wam custom segments + webo_audiences: [...], // wam audiences + }, + enabled: true, + }, + sfbxLiteDataConf: { + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + sendToBidders: true, // override param.sendToBidders. default is true + defaultProfile: { // optional, used if nothing is found + /* add specific lite segments here */ + }, + enabled: true, + }, + } + }] + } + }); +}); +``` + +Imagine we need to configure the following options using the previous example, we can write the configuration like the one below. + +||contextual|wam|lite| +| :------------ | :------------ | :------------ |:------------ | +|setPrebidTargeting|true|false|true| +|sendToBidders|false|true|true| + +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function () { + pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + setPrebidTargeting: false, // optional. set the default value of each section. + sendToBidders: false, // optional. set the default value of each section. + weboCtxConf: { + token: "<>", // mandatory + targetURL: "https://example.org", // default is document.URL + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + enabled: true, + }, + weboUserDataConf: { + sendToBidders: true, // override param.sendToBidders. default is true + enabled: true, + }, + sfbxLiteDataConf: { + setPrebidTargeting: true, // override param.setPrebidTargeting. default is true + sendToBidders: true, // override param.sendToBidders. default is true + enabled: true, + }, + } + }] + } + }); +}); +``` + +We can also define a list of adunits / bidders that will receive data instead of using boolean values. + +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function () { + pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + weboCtxConf: { + token: "to-be-defined", // mandatory + setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits + sendToBidders: ['appnexus',...], // overide, send to only some bidders + enabled: true, + }, + weboUserDataConf: { + accountId: 12345, // recommended + setPrebidTargeting: ['adUnitCode2',...], // set target only on certain adunits + sendToBidders: ['rubicon',...], // overide, send to only some bidders + enabled: true, + }, + sfbxLiteDataConf: { + setPrebidTargeting: ['adUnitCode3',...], // set target only on certain adunits + sendToBidders: ['smartadserver',...], // overide, send to only some bidders + enabled: true, + } + } + }] + } + }); +}); +``` + +Finally, we can combine several styles in the same configuration if needed. Including the callback style. + +```javascript +var pbjs = pbjs || {}; +pbjs.que = pbjs.que || []; + +pbjs.que.push(function () { + pbjs.setConfig({ + debug: true, + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: "weborama", + waitForIt: true, + params: { + setPrebidTargeting: true, // optional + sendToBidders: true, // optional + onData: function(data, meta){ // optional + var userCentricData = meta.user; // maybe undefined + var sourceOfData = meta.source; // contextual, wam or lite + + var isDefault = meta.isDefault; // true if uses default profile + + console.log('onData', data, meta); + }, + weboCtxConf: { + token: "to-be-defined", // mandatory + targetURL: "https://prebid.org", // default is document.URL + setPrebidTargeting: true, // override param.setPrebidTargeting or default true + sendToBidders: ['appnexus',...], // overide, send to only some bidders + defaultProfile: { // optional + webo_ctx: ['moon'], + webo_ds: ['bar'] + }, + enabled: true, + //, onData: function (data, ...) { ...} + }, + weboUserDataConf: { + accountId: 12345, // recommended + setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits + sendToBidders: { // send to only some bidders and adunits + 'appnexus': true, // all adunits for appnexus + 'pubmatic': ['adUnitCode1',...] // some adunits for pubmatic + // other bidders will be ignored + }, + defaultProfile: { // optional + webo_cs: ['Red'], + webo_audiences: ['bam'] + }, + localStorageProfileKey: 'webo_wam2gam_entry', // default + enabled: true, + //, onData: function (data, ...) { ...} + }, + sfbxLiteDataConf: { + setPrebidTargeting: function(adUnitCode){ // specify set target via callback + return adUnitCode == 'adUnitCode1'; + }, + sendToBidders: function(bid, adUnitCode){ // specify sendToBidders via callback + return bid.bidder == 'appnexus' && adUnitCode == 'adUnitCode1'; + } + defaultProfile: { // optional + lite_occupation: ['gérant', 'bénévole'], + lite_hobbies: ['sport', 'cinéma'], + }, + localStorageProfileKey: '_lite', // default + enabled: true, + //, onData: function (data, ...) { ...} + } + } + }] + } + }); +}); +``` ### Supported Bidders We currently support the following bidder adapters: + * SmartADServer SSP * PubMatic SSP * AppNexus SSP * Rubicon SSP -We also set the bidder and global ortb2 `site` and `user` sections. The following bidders may support it, to be sure, check the `First Party Data Support` on the feature list for the particular bidder from here: https://docs.prebid.org/dev-docs/bidders +We also set the bidder (and global, if no specific bidders are set on `sendToBidders`) ortb2 `site.ext.data` and `user.ext.data` sections (as arbitrary data). The following bidders may support it, to be sure, check the `First Party Data Support` on the feature list for the particular bidder from [here](https://docs.prebid.org/dev-docs/bidders). * Adagio * AdformOpenRTB 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..3b86e5b59dc 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'; @@ -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/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..926c3317f1d 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,7 +277,7 @@ function generateOpenRtbObject(bidderRequest, bid) { outBoundBidRequest.site.id = bid.params.dcn; }; - if (config.getConfig('ortb2')) { + if (bidderRequest.ortb2) { outBoundBidRequest = appendFirstPartyData(outBoundBidRequest, bid); }; @@ -376,7 +369,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; @@ -547,7 +540,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 new file mode 100644 index 00000000000..ae2fb16c8ac --- /dev/null +++ b/modules/yandexBidAdapter.js @@ -0,0 +1,107 @@ +import {formatQS, deepAccess} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'yandex'; +const BIDDER_URL = 'https://bs-metadsp.yandex.ru/metadsp'; +const DEFAULT_TTL = 180; +const SSP_ID = 10500; + +export const spec = { + code: BIDDER_CODE, + aliases: ['ya'], // short code + + isBidRequestValid: function(bid) { + return !!(bid.params && bid.params.pageId && bid.params.impId); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); + const consentString = deepAccess(bidderRequest, 'gdprConsent.consentString'); + + let referrer = ''; + if (bidderRequest && bidderRequest.refererInfo) { + // TODO: is 'domain' the right value here? + referrer = bidderRequest.refererInfo.domain + } + + return validBidRequests.map((bidRequest) => { + const { params } = bidRequest; + const { pageId, impId, targetRef, withCredentials = true } = params; + + const queryParams = { + 'imp-id': impId, + 'target-ref': targetRef || referrer, + 'ssp-id': SSP_ID, + }; + if (gdprApplies !== undefined) { + queryParams['gdpr'] = 1; + queryParams['tcf-consent'] = consentString; + } + const imp = { + id: impId, + }; + + const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); + if (bannerParams) { + const [ w, h ] = bannerParams.sizes[0]; + imp.banner = { + w, + h, + }; + } + + const queryParamsString = formatQS(queryParams); + return { + method: 'POST', + url: BIDDER_URL + `/${pageId}?${queryParamsString}`, + data: { + id: bidRequest.bidId, + imp: [imp], + site: { + page: referrer, + }, + }, + options: { + withCredentials, + }, + bidRequest, + } + }); + }, + + interpretResponse: function(serverResponse, {bidRequest}) { + let response = serverResponse.body; + if (!response.seatbid) { + return []; + } + + const { cur, seatbid } = serverResponse.body; + const rtbBids = seatbid + .map(seatbid => seatbid.bid) + .reduce((a, b) => a.concat(b), []); + + return rtbBids.map(rtbBid => { + let prBid = { + requestId: bidRequest.bidId, + cpm: rtbBid.price, + currency: cur || 'USD', + width: rtbBid.w, + height: rtbBid.h, + creativeId: rtbBid.adid, + + netRevenue: true, + ttl: DEFAULT_TTL, + + meta: { + advertiserDomains: rtbBid.adomain && rtbBid.adomain.length > 0 ? rtbBid.adomain : [], + } + }; + + prBid.ad = rtbBid.adm; + + return prBid; + }); + }, +} + +registerBidder(spec); diff --git a/modules/yandexBidAdapter.md b/modules/yandexBidAdapter.md new file mode 100644 index 00000000000..7a51d7bc5fb --- /dev/null +++ b/modules/yandexBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Yandex Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@yandex-team.com +``` + +# Description + +Yandex Bidder Adapter for Prebid.js. + +# Parameters + +| Name | Scope | Description | Example | Type | +|---------------|----------|-------------------------|-----------|-----------| +| `pageId` | required | Page ID | `123` | `Integer` | +| `impId` | required | Block ID | `1` | `Integer` | + +# Test Parameters + +``` +var adUnits = [{ + code: 'banner-1', + mediaTypes: { + banner: { + sizes: [[240, 400]], + } + }, + bids: [{ + { + bidder: 'yandex', + params: { + pageId: 346580, + impId: 143, + }, + } + }] +}]; +``` diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index fd3540dce58..bcaffb35842 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -1,9 +1,8 @@ -import {_each, deepAccess, isArray, isPlainObject} 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 { _each, deepAccess, isArray, 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' const ENDPOINT = 'https://ad.yieldlab.net' const BIDDER_CODE = 'yieldlab' @@ -11,12 +10,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 +30,13 @@ 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) { const adslotIds = [] + const adslotSizes = []; const timestamp = Date.now() const query = { ts: timestamp, @@ -39,6 +45,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 +59,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] } } @@ -61,8 +74,9 @@ export const spec = { }) 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 +88,9 @@ export const spec = { } const adslots = adslotIds.join(',') + if (adslotSizes.length > 0) { + query.sizes = adslotSizes.join(',') + } const queryString = createQueryString(query) return { @@ -86,8 +103,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 +117,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,6 +168,7 @@ 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 @@ -174,6 +193,37 @@ export const spec = { } }) 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. + * @param {Object} gdprConsent Is the GDPR Consent object wrapping gdprApplies {boolean} and consentString {string} attributes. + * @param {string} uspConsent Is the US Privacy Consent string. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + + if (syncOptions.iframeEnabled) { + const params = []; + params.push(`ts=${timestamp()}`); + params.push(`type=h`) + if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { + params.push(`gdpr=${Number(gdprConsent.gdprApplies)}`); + } + if (gdprConsent && (typeof gdprConsent.consentString === 'string')) { + params.push(`gdpr_consent=${gdprConsent.consentString}`); + } + syncs.push({ + type: 'iframe', + url: `${ENDPOINT}/d/6846326/766/2x2?${params.join('&')}` + }); + } + + return syncs; } }; @@ -203,7 +253,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') } @@ -213,7 +263,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 } @@ -223,7 +273,7 @@ function getPlayerSize(format) { * @returns {Array} */ function parseSize(size) { - return size.split('x').map(Number) + return size.split(DIMENSION_SIGN).map(Number) } /** @@ -232,7 +282,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) } @@ -245,10 +295,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 { @@ -265,11 +315,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) } } @@ -303,8 +353,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 } @@ -318,8 +368,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]) @@ -351,5 +401,40 @@ function outstreamRender(bid) { window.document.dispatchEvent(new Event('ma-start-event')) }); } +/** + * 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) +} registerBidder(spec) diff --git a/modules/yieldliftBidAdapter.js b/modules/yieldliftBidAdapter.js index 61b99d95605..fddc9a0d50b 100644 --- a/modules/yieldliftBidAdapter.js +++ b/modules/yieldliftBidAdapter.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: { @@ -96,9 +96,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..a0d634e34b4 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); @@ -352,7 +371,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$', }, @@ -445,13 +464,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']; 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..126e3504f98 100644 --- a/modules/yieldoneAnalyticsAdapter.js +++ b/modules/yieldoneAnalyticsAdapter.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..98a95b6758e 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,6 +73,19 @@ 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, diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index 6872820dd48..8ff22faa7f4 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.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_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 87fdbe1396f..86b28021bab 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -2,6 +2,7 @@ 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'; @@ -89,13 +90,12 @@ export const spec = { if (!impData.banner && !impData.video) { impData.banner = buildBanner(request); } - const fpd = config.getLegacyFpd(config.getConfig('ortb2')) || {}; let payload = { id: bidderRequest.auctionId, cur: [DEFAULT_CUR], imp: [impData], 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 +104,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; @@ -126,9 +127,10 @@ export const spec = { } provideEids(request, payload); + const url = params.shortname ? ENDPOINT_URL.concat('?shortname=', params.shortname) : ENDPOINT_URL; return { method: 'POST', - url: ENDPOINT_URL, + url: url, data: JSON.stringify(payload), }; }, @@ -268,16 +270,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 9fbd4aea6a8..19a4ed0770d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,16 @@ { "name": "prebid.js", - "version": "6.18.0-pre", + "version": "7.8.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "6.15.0-pre", + "version": "7.4.0-pre", "license": "Apache-2.0", "dependencies": { + "@babel/core": "^7.16.7", + "@babel/preset-env": "^7.16.8", "babel-plugin-transform-object-assign": "^6.22.0", "core-js": "^3.13.0", "core-js-pure": "^3.13.0", @@ -19,23 +21,22 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "2.0.0" + "live-connect-js": "2.3.3" }, "devDependencies": { - "@babel/core": "^7.16.7", "@babel/eslint-parser": "^7.16.5", - "@babel/preset-env": "^7.16.8", "@jsdevtools/coverage-istanbul-loader": "^3.0.3", "@wdio/browserstack-service": "^7.16.0", "@wdio/cli": "^7.5.2", "@wdio/concise-reporter": "^7.5.2", "@wdio/local-runner": "^7.5.2", "@wdio/mocha-framework": "^7.5.2", - "@wdio/spec-reporter": "^7.5.2", + "@wdio/spec-reporter": "^7.19.0", "@wdio/sync": "^7.5.2", - "ajv": "5.5.2", + "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", + "babel-register": "^6.26.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", @@ -57,8 +58,6 @@ "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-if": "^3.0.0", "gulp-js-escape": "^1.0.1", "gulp-replace": "^1.0.0", @@ -113,7 +112,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", - "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.0" }, @@ -125,7 +123,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, "dependencies": { "@babel/highlight": "^7.16.7" }, @@ -137,7 +134,6 @@ "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==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -146,7 +142,6 @@ "version": "7.17.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", @@ -194,7 +189,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", - "dev": true, "dependencies": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -208,7 +202,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -220,7 +213,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", - "dev": true, "dependencies": { "@babel/helper-explode-assignable-expression": "^7.16.7", "@babel/types": "^7.16.7" @@ -233,7 +225,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", - "dev": true, "dependencies": { "@babel/compat-data": "^7.16.4", "@babel/helper-validator-option": "^7.16.7", @@ -251,7 +242,6 @@ "version": "7.17.6", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", - "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-environment-visitor": "^7.16.7", @@ -272,7 +262,6 @@ "version": "7.17.0", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==", - "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.16.7", "regexpu-core": "^5.0.1" @@ -288,7 +277,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", - "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.13.0", "@babel/helper-module-imports": "^7.12.13", @@ -307,7 +295,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -319,7 +306,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -331,7 +317,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, "dependencies": { "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", @@ -345,7 +330,6 @@ "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==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -357,7 +341,6 @@ "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==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -369,7 +352,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -381,7 +363,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -393,7 +374,6 @@ "version": "7.17.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", - "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", @@ -412,7 +392,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -424,7 +403,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -433,7 +411,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", - "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-wrap-function": "^7.16.8", @@ -447,7 +424,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", - "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-member-expression-to-functions": "^7.16.7", @@ -463,7 +439,6 @@ "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==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -475,7 +450,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "dev": true, "dependencies": { "@babel/types": "^7.16.0" }, @@ -487,7 +461,6 @@ "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==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -499,7 +472,6 @@ "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==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -508,7 +480,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -517,7 +488,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", - "dev": true, "dependencies": { "@babel/helper-function-name": "^7.16.7", "@babel/template": "^7.16.7", @@ -532,7 +502,6 @@ "version": "7.17.2", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", - "dev": true, "dependencies": { "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.0", @@ -546,7 +515,6 @@ "version": "7.16.10", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", @@ -560,7 +528,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -572,7 +539,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -587,7 +553,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", @@ -604,7 +569,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-remap-async-to-generator": "^7.16.8", @@ -621,7 +585,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", - "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -637,7 +600,6 @@ "version": "7.17.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz", "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==", - "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.17.6", "@babel/helper-plugin-utils": "^7.16.7", @@ -654,7 +616,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" @@ -670,7 +631,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" @@ -686,7 +646,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-json-strings": "^7.8.3" @@ -702,7 +661,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" @@ -718,7 +676,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -734,7 +691,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -750,7 +706,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==", - "dev": true, "dependencies": { "@babel/compat-data": "^7.17.0", "@babel/helper-compilation-targets": "^7.16.7", @@ -769,7 +724,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -785,7 +739,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", @@ -802,7 +755,6 @@ "version": "7.16.11", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", - "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.16.10", "@babel/helper-plugin-utils": "^7.16.7" @@ -818,7 +770,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", - "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-create-class-features-plugin": "^7.16.7", @@ -836,7 +787,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", - "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -852,7 +802,6 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -864,7 +813,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -876,7 +824,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -891,7 +838,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -903,7 +849,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.3" }, @@ -915,7 +860,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -927,7 +871,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -939,7 +882,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -951,7 +893,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -963,7 +904,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -975,7 +915,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -987,7 +926,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -999,7 +937,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1014,7 +951,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1029,7 +965,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1044,7 +979,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", - "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7", @@ -1061,7 +995,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1076,7 +1009,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1091,7 +1023,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", - "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-environment-visitor": "^7.16.7", @@ -1113,7 +1044,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1128,7 +1058,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1143,7 +1072,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", - "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -1159,7 +1087,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1174,7 +1101,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", - "dev": true, "dependencies": { "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -1190,7 +1116,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1205,7 +1130,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", - "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.16.7", "@babel/helper-function-name": "^7.16.7", @@ -1222,7 +1146,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1237,7 +1160,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1252,7 +1174,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", - "dev": true, "dependencies": { "@babel/helper-module-transforms": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7", @@ -1269,7 +1190,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", - "dev": true, "dependencies": { "@babel/helper-module-transforms": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7", @@ -1287,7 +1207,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", - "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-module-transforms": "^7.16.7", @@ -1306,7 +1225,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", - "dev": true, "dependencies": { "@babel/helper-module-transforms": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -1322,7 +1240,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", - "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.16.7" }, @@ -1337,7 +1254,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1352,7 +1268,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-replace-supers": "^7.16.7" @@ -1368,7 +1283,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1383,7 +1297,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1398,7 +1311,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", - "dev": true, "dependencies": { "regenerator-transform": "^0.14.2" }, @@ -1413,7 +1325,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1428,7 +1339,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1443,7 +1353,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" @@ -1459,7 +1368,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1474,7 +1382,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1489,7 +1396,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1504,7 +1410,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.16.7" }, @@ -1519,7 +1424,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", - "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -1535,7 +1439,6 @@ "version": "7.16.11", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", - "dev": true, "dependencies": { "@babel/compat-data": "^7.16.8", "@babel/helper-compilation-targets": "^7.16.7", @@ -1623,7 +1526,6 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", @@ -1639,7 +1541,6 @@ "version": "7.17.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -1650,14 +1551,12 @@ "node_modules/@babel/runtime/node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "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==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/parser": "^7.16.7", @@ -1671,7 +1570,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.17.3", @@ -1692,7 +1590,6 @@ "version": "7.17.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" @@ -1746,12 +1643,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.12.1", "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", @@ -1767,12 +1658,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -2021,7 +1906,6 @@ "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==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2029,14 +1913,12 @@ "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==", - "dev": true + "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==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -3289,14 +3171,14 @@ } }, "node_modules/@wdio/spec-reporter": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.16.14.tgz", - "integrity": "sha512-hpS2rLXo91lfrit5/pjDSbff2lqQe+k07/JPOJ48W+ZPSI+ib7rSldI4JFYU4YuKN1TnhkbhxRBMib3bF3Fs+Q==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.19.1.tgz", + "integrity": "sha512-qnZkn3VcyBPtcorUtpyCFE8v5ubyWmR7mFETXNzyriHyvjvk+NeFCWaFcIehpXYXiAmNpAwyfnZoIY6tkKQixQ==", "dev": true, "dependencies": { "@types/easy-table": "^0.0.33", - "@wdio/reporter": "7.16.14", - "@wdio/types": "7.16.14", + "@wdio/reporter": "7.19.1", + "@wdio/types": "7.19.1", "chalk": "^4.0.0", "easy-table": "^1.1.1", "pretty-ms": "^7.0.0" @@ -3308,6 +3190,58 @@ "@wdio/cli": "^7.0.0" } }, + "node_modules/@wdio/spec-reporter/node_modules/@wdio/reporter": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.19.1.tgz", + "integrity": "sha512-sWmBBV4dPCZkGk9Qq0m35T/vHGen0N10nH4osQcVP3IZJqpo2eLIH4w+X6EUbjZ2GdgOA2bLMMzb1bl9JqnGPg==", + "dev": true, + "dependencies": { + "@types/diff": "^5.0.0", + "@types/node": "^17.0.4", + "@types/object-inspect": "^1.8.0", + "@types/supports-color": "^8.1.0", + "@types/tmp": "^0.2.0", + "@wdio/types": "7.19.1", + "diff": "^5.0.0", + "fs-extra": "^10.0.0", + "object-inspect": "^1.10.3", + "supports-color": "8.1.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/@wdio/reporter/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/@wdio/types": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.19.1.tgz", + "integrity": "sha512-mOodKlmvYxpj8P5BhjggEGpXuiRSlsyn2ClG8QqJ3lfXgOtOVEzFNfv/Ai7TkHr+lHDQNXLjllCjSqoCHhwlqg==", + "dev": true, + "dependencies": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "^4.6.2" + } + }, "node_modules/@wdio/spec-reporter/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3646,24 +3580,19 @@ } }, "node_modules/ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, "dependencies": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "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" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/amdefine": { @@ -3737,7 +3666,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -4308,6 +4236,113 @@ "node": ">=0.8.0" } }, + "node_modules/babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "node_modules/babel-core/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/babel-core/node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/babel-core/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/babel-core/node_modules/slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "dependencies": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "node_modules/babel-generator/node_modules/detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "dependencies": { + "repeating": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-generator/node_modules/jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, "node_modules/babel-loader": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", @@ -4353,11 +4388,19 @@ "node": ">=4.0.0" } }, + "node_modules/babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, "node_modules/babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, "dependencies": { "object.assign": "^4.1.0" } @@ -4366,7 +4409,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", - "dev": true, "dependencies": { "@babel/compat-data": "^7.13.11", "@babel/helper-define-polyfill-provider": "^0.3.1", @@ -4380,7 +4422,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", - "dev": true, "dependencies": { "@babel/helper-define-polyfill-provider": "^0.3.1", "core-js-compat": "^3.21.0" @@ -4393,7 +4434,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dev": true, "dependencies": { "@babel/helper-define-polyfill-provider": "^0.3.1" }, @@ -4409,6 +4449,50 @@ "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", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "dependencies": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + } + }, + "node_modules/babel-register/node_modules/core-js": { + "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.", + "dev": true, + "hasInstallScript": true + }, + "node_modules/babel-register/node_modules/mkdirp": { + "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.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/babel-register/node_modules/source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "dependencies": { + "source-map": "^0.5.6" + } + }, "node_modules/babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -4425,6 +4509,81 @@ "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.", "hasInstallScript": true }, + "node_modules/babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/babel-traverse/node_modules/globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-traverse/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "node_modules/babel-types/node_modules/to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/babelify": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", @@ -4437,6 +4596,15 @@ "@babel/core": "^7.0.0" } }, + "node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "bin": { + "babylon": "bin/babylon.js" + } + }, "node_modules/bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -4778,7 +4946,6 @@ "version": "4.20.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.0.tgz", "integrity": "sha512-bnpOoa+DownbciXj0jVGENf8VYQnE2LNWomhYuCsMmmx9Jd9lwq0WXODuwpSsp8AVdKM2/HorrzxAfbKvWTByQ==", - "dev": true, "dependencies": { "caniuse-lite": "^1.0.30001313", "electron-to-chromium": "^1.4.76", @@ -5279,7 +5446,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -5354,14 +5520,19 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001314", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz", - "integrity": "sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "version": "1.0.30001320", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz", + "integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] }, "node_modules/caseless": { "version": "0.12.0", @@ -5413,7 +5584,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -5427,7 +5597,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, "engines": { "node": ">=4" } @@ -5436,7 +5605,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -5811,7 +5979,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -5819,8 +5986,7 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "node_modules/color-support": { "version": "1.1.3", @@ -6434,7 +6600,6 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, "dependencies": { "safe-buffer": "~5.1.1" } @@ -6485,7 +6650,6 @@ "version": "3.21.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", - "dev": true, "dependencies": { "browserslist": "^4.19.1", "semver": "7.0.0" @@ -6499,7 +6663,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -6738,7 +6901,6 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -6943,7 +7105,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "dependencies": { "object-keys": "^1.0.12" }, @@ -7689,8 +7850,7 @@ "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==", - "dev": true + "integrity": "sha512-o61+D/Lx7j/E0LIin/efOqeHpXhwi1TaQco9vUcRmr91m25SfZY6L5hWJDv/r+6kNjboFKgBw1LbfM0lbhuK6Q==" }, "node_modules/emoji-regex": { "version": "6.1.1", @@ -7976,7 +8136,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -7990,7 +8149,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -8460,12 +8618,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "node_modules/eslint/node_modules/globals": { "version": "13.12.1", "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", @@ -8481,12 +8633,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/eslint/node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -8636,7 +8782,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9080,6 +9225,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", @@ -9198,9 +9355,9 @@ } }, "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=", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "node_modules/fast-json-stable-stringify": { @@ -9750,8 +9907,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -9775,7 +9931,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -9802,7 +9957,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -10399,7 +10553,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "engines": { "node": ">=4" } @@ -11503,6 +11656,27 @@ "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" + } + }, "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", @@ -11678,6 +11852,12 @@ "node": ">=0.12" } }, + "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", @@ -11755,6 +11935,12 @@ "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", @@ -11958,6 +12144,18 @@ "string-width": "^2.1.1" } }, + "node_modules/gulp-eslint/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/gulp-eslint/node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -11988,38 +12186,6 @@ "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", @@ -12544,18 +12710,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/har-validator/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/har-validator/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -12569,7 +12723,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -12632,7 +12785,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -12773,6 +12925,19 @@ "node": "*" } }, + "node_modules/home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -13102,6 +13267,15 @@ "node": ">= 0.10" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -13278,7 +13452,6 @@ "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==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -14361,8 +14534,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "3.14.1", @@ -14400,7 +14572,6 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -14433,9 +14604,9 @@ "dev": true }, "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=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { @@ -14454,7 +14625,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, "dependencies": { "minimist": "^1.2.5" }, @@ -14929,18 +15099,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", @@ -15148,9 +15306,9 @@ "dev": true }, "node_modules/live-connect-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.0.0.tgz", - "integrity": "sha512-Xhrj1JU5LoLjJuujjTlvDfc/n3Shzk2hPlYmLdCx/lsltFFVuCFa9uM8u5mcHlmOUKP5pu9I54bAITxZBMHoXg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.3.3.tgz", + "integrity": "sha512-WfY6v1jVutW/OGPvm0OaWAHc0MvB5MDdSuniZ+n9cpCltArWHTJtAsjWe8T+ACmdLAlZS9z7hzL3ntLnq+J0yQ==", "dependencies": { "tiny-hashes": "1.0.1" }, @@ -15351,8 +15509,7 @@ "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, "node_modules/lodash.defaults": { "version": "4.2.0", @@ -15587,6 +15744,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -16645,10 +16814,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "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", @@ -16970,8 +17138,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multipipe": { "version": "0.1.2", @@ -17192,8 +17359,7 @@ "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==", - "dev": true + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, "node_modules/nopt": { "version": "3.0.6", @@ -17438,7 +17604,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -17459,7 +17624,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", @@ -17743,6 +17907,15 @@ "readable-stream": "^2.0.1" } }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -17991,8 +18164,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-root": { "version": "0.1.1", @@ -18074,8 +18246,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -18339,6 +18510,15 @@ "node": ">=0.8" } }, + "node_modules/private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -18852,14 +19032,12 @@ "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "node_modules/regenerate-unicode-properties": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", - "dev": true, "dependencies": { "regenerate": "^1.4.2" }, @@ -18876,7 +19054,6 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dev": true, "dependencies": { "@babel/runtime": "^7.8.4" } @@ -18926,7 +19103,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", - "dev": true, "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.0.1", @@ -18942,14 +19118,12 @@ "node_modules/regjsgen": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", - "dev": true + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" }, "node_modules/regjsparser": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "dev": true, "dependencies": { "jsesc": "~0.5.0" }, @@ -18961,7 +19135,6 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true, "bin": { "jsesc": "bin/jsesc" } @@ -19273,7 +19446,6 @@ "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, "dependencies": { "is-core-module": "^2.8.1", "path-parse": "^1.0.7", @@ -19448,8 +19620,7 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/safe-json-parse": { "version": "1.0.1", @@ -19521,23 +19692,10 @@ "ajv": "^6.9.1" } }, - "node_modules/schema-utils/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -20187,7 +20345,6 @@ "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" } @@ -20938,7 +21095,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -20988,12 +21144,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/table/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "node_modules/table/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -21174,18 +21324,6 @@ "ajv": "^6.9.1" } }, - "node_modules/terser-webpack-plugin/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -21364,15 +21502,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": { @@ -21392,7 +21530,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, "engines": { "node": ">=4" } @@ -21539,6 +21676,15 @@ "node": ">=8" } }, + "node_modules/trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/trough": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", @@ -21654,6 +21800,20 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "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==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/typescript-compare": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", @@ -21781,7 +21941,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, "engines": { "node": ">=4" } @@ -21790,7 +21949,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -21803,7 +21961,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true, "engines": { "node": ">=4" } @@ -21812,7 +21969,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true, "engines": { "node": ">=4" } @@ -22809,18 +22965,6 @@ "ajv": "^6.9.1" } }, - "node_modules/webpack/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -23325,6 +23469,7 @@ } }, "plugins/eslint": { + "name": "eslint-plugin-prebid", "version": "1.0.0", "dev": true, "license": "Apache-2.0" @@ -23335,7 +23480,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", - "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.0" } @@ -23344,7 +23488,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, "requires": { "@babel/highlight": "^7.16.7" } @@ -23352,14 +23495,12 @@ "@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==", - "dev": true + "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==" }, "@babel/core": { "version": "7.17.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", - "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", @@ -23393,7 +23534,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", - "dev": true, "requires": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -23404,7 +23544,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -23413,7 +23552,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", - "dev": true, "requires": { "@babel/helper-explode-assignable-expression": "^7.16.7", "@babel/types": "^7.16.7" @@ -23423,7 +23561,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", - "dev": true, "requires": { "@babel/compat-data": "^7.16.4", "@babel/helper-validator-option": "^7.16.7", @@ -23435,7 +23572,6 @@ "version": "7.17.6", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-environment-visitor": "^7.16.7", @@ -23450,7 +23586,6 @@ "version": "7.17.0", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz", "integrity": "sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.16.7", "regexpu-core": "^5.0.1" @@ -23460,7 +23595,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", - "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.13.0", "@babel/helper-module-imports": "^7.12.13", @@ -23476,7 +23610,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -23485,7 +23618,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -23494,7 +23626,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", @@ -23505,7 +23636,6 @@ "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==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -23514,7 +23644,6 @@ "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==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -23523,7 +23652,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -23532,7 +23660,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -23541,7 +23668,6 @@ "version": "7.17.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", - "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", @@ -23557,7 +23683,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -23565,14 +23690,12 @@ "@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==", - "dev": true + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" }, "@babel/helper-remap-async-to-generator": { "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-wrap-function": "^7.16.8", @@ -23583,7 +23706,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", - "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-member-expression-to-functions": "^7.16.7", @@ -23596,7 +23718,6 @@ "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==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -23605,7 +23726,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "dev": true, "requires": { "@babel/types": "^7.16.0" } @@ -23614,7 +23734,6 @@ "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==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } @@ -23622,20 +23741,17 @@ "@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==", - "dev": true + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" }, "@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==", - "dev": true + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" }, "@babel/helper-wrap-function": { "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", - "dev": true, "requires": { "@babel/helper-function-name": "^7.16.7", "@babel/template": "^7.16.7", @@ -23647,7 +23763,6 @@ "version": "7.17.2", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", - "dev": true, "requires": { "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.0", @@ -23658,7 +23773,6 @@ "version": "7.16.10", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", @@ -23668,14 +23782,12 @@ "@babel/parser": { "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", - "dev": true + "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -23684,7 +23796,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", @@ -23695,7 +23806,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-remap-async-to-generator": "^7.16.8", @@ -23706,7 +23816,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", - "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -23716,7 +23825,6 @@ "version": "7.17.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz", "integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==", - "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.17.6", "@babel/helper-plugin-utils": "^7.16.7", @@ -23727,7 +23835,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" @@ -23737,7 +23844,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" @@ -23747,7 +23853,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-json-strings": "^7.8.3" @@ -23757,7 +23862,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" @@ -23767,7 +23871,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -23777,7 +23880,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -23787,7 +23889,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz", "integrity": "sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw==", - "dev": true, "requires": { "@babel/compat-data": "^7.17.0", "@babel/helper-compilation-targets": "^7.16.7", @@ -23800,7 +23901,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -23810,7 +23910,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", @@ -23821,7 +23920,6 @@ "version": "7.16.11", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", - "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.16.10", "@babel/helper-plugin-utils": "^7.16.7" @@ -23831,7 +23929,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-create-class-features-plugin": "^7.16.7", @@ -23843,7 +23940,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -23853,7 +23949,6 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -23862,7 +23957,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -23871,7 +23965,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.14.5" } @@ -23880,7 +23973,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -23889,7 +23981,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" } @@ -23898,7 +23989,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -23907,7 +23997,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -23916,7 +24005,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -23925,7 +24013,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -23934,7 +24021,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -23943,7 +24029,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -23952,7 +24037,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -23961,7 +24045,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.14.5" } @@ -23970,7 +24053,6 @@ "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.14.5" } @@ -23979,7 +24061,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -23988,7 +24069,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", - "dev": true, "requires": { "@babel/helper-module-imports": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7", @@ -23999,7 +24079,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24008,7 +24087,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24017,7 +24095,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-environment-visitor": "^7.16.7", @@ -24033,7 +24110,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24042,7 +24118,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.3.tgz", "integrity": "sha512-dDFzegDYKlPqa72xIlbmSkly5MluLoaC1JswABGktyt6NTXSBcUuse/kWE/wvKFWJHPETpi158qJZFS3JmykJg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24051,7 +24126,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -24061,7 +24135,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24070,7 +24143,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", - "dev": true, "requires": { "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -24080,7 +24152,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24089,7 +24160,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", - "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.16.7", "@babel/helper-function-name": "^7.16.7", @@ -24100,7 +24170,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24109,7 +24178,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24118,7 +24186,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", - "dev": true, "requires": { "@babel/helper-module-transforms": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7", @@ -24129,7 +24196,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", - "dev": true, "requires": { "@babel/helper-module-transforms": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7", @@ -24141,7 +24207,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", - "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-module-transforms": "^7.16.7", @@ -24154,7 +24219,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", - "dev": true, "requires": { "@babel/helper-module-transforms": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -24164,7 +24228,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.16.7" } @@ -24173,7 +24236,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24182,7 +24244,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-replace-supers": "^7.16.7" @@ -24192,7 +24253,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24201,7 +24261,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24210,7 +24269,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", - "dev": true, "requires": { "regenerator-transform": "^0.14.2" } @@ -24219,7 +24277,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24228,7 +24285,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24237,7 +24293,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" @@ -24247,7 +24302,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24256,7 +24310,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24265,7 +24318,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24274,7 +24326,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.16.7" } @@ -24283,7 +24334,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.16.7", "@babel/helper-plugin-utils": "^7.16.7" @@ -24293,7 +24343,6 @@ "version": "7.16.11", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", - "dev": true, "requires": { "@babel/compat-data": "^7.16.8", "@babel/helper-compilation-targets": "^7.16.7", @@ -24375,7 +24424,6 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", @@ -24388,7 +24436,6 @@ "version": "7.17.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.4" }, @@ -24396,8 +24443,7 @@ "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" } } }, @@ -24405,7 +24451,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/parser": "^7.16.7", @@ -24416,7 +24461,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", - "dev": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.17.3", @@ -24434,7 +24478,6 @@ "version": "7.17.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" @@ -24475,12 +24518,6 @@ "uri-js": "^4.2.2" } }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "globals": { "version": "13.12.1", "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", @@ -24490,12 +24527,6 @@ "type-fest": "^0.20.2" } }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -24684,20 +24715,17 @@ "@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==", - "dev": true + "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==" }, "@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==", - "dev": true + "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==", - "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -25749,19 +25777,58 @@ } }, "@wdio/spec-reporter": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.16.14.tgz", - "integrity": "sha512-hpS2rLXo91lfrit5/pjDSbff2lqQe+k07/JPOJ48W+ZPSI+ib7rSldI4JFYU4YuKN1TnhkbhxRBMib3bF3Fs+Q==", + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.19.1.tgz", + "integrity": "sha512-qnZkn3VcyBPtcorUtpyCFE8v5ubyWmR7mFETXNzyriHyvjvk+NeFCWaFcIehpXYXiAmNpAwyfnZoIY6tkKQixQ==", "dev": true, "requires": { "@types/easy-table": "^0.0.33", - "@wdio/reporter": "7.16.14", - "@wdio/types": "7.16.14", + "@wdio/reporter": "7.19.1", + "@wdio/types": "7.19.1", "chalk": "^4.0.0", "easy-table": "^1.1.1", "pretty-ms": "^7.0.0" }, "dependencies": { + "@wdio/reporter": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.19.1.tgz", + "integrity": "sha512-sWmBBV4dPCZkGk9Qq0m35T/vHGen0N10nH4osQcVP3IZJqpo2eLIH4w+X6EUbjZ2GdgOA2bLMMzb1bl9JqnGPg==", + "dev": true, + "requires": { + "@types/diff": "^5.0.0", + "@types/node": "^17.0.4", + "@types/object-inspect": "^1.8.0", + "@types/supports-color": "^8.1.0", + "@types/tmp": "^0.2.0", + "@wdio/types": "7.19.1", + "diff": "^5.0.0", + "fs-extra": "^10.0.0", + "object-inspect": "^1.10.3", + "supports-color": "8.1.1" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@wdio/types": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.19.1.tgz", + "integrity": "sha512-mOodKlmvYxpj8P5BhjggEGpXuiRSlsyn2ClG8QqJ3lfXgOtOVEzFNfv/Ai7TkHr+lHDQNXLjllCjSqoCHhwlqg==", + "dev": true, + "requires": { + "@types/node": "^17.0.4", + "got": "^11.8.1" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -26058,24 +26125,17 @@ "dev": true }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "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": {} - }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", @@ -26123,7 +26183,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -26564,6 +26623,105 @@ } } }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + } + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, "babel-loader": { "version": "8.2.3", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", @@ -26598,11 +26756,19 @@ } } }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, "babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, "requires": { "object.assign": "^4.1.0" } @@ -26611,7 +26777,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", - "dev": true, "requires": { "@babel/compat-data": "^7.13.11", "@babel/helper-define-polyfill-provider": "^0.3.1", @@ -26622,7 +26787,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz", "integrity": "sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==", - "dev": true, "requires": { "@babel/helper-define-polyfill-provider": "^0.3.1", "core-js-compat": "^3.21.0" @@ -26632,7 +26796,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dev": true, "requires": { "@babel/helper-define-polyfill-provider": "^0.3.1" } @@ -26645,6 +26808,47 @@ "babel-runtime": "^6.22.0" } }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + }, + "dependencies": { + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true + }, + "mkdirp": { + "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.6" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + } + } + }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -26661,6 +26865,79 @@ } } }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + } + } + }, "babelify": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", @@ -26668,6 +26945,12 @@ "dev": true, "requires": {} }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -26953,7 +27236,6 @@ "version": "4.20.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.0.tgz", "integrity": "sha512-bnpOoa+DownbciXj0jVGENf8VYQnE2LNWomhYuCsMmmx9Jd9lwq0WXODuwpSsp8AVdKM2/HorrzxAfbKvWTByQ==", - "dev": true, "requires": { "caniuse-lite": "^1.0.30001313", "electron-to-chromium": "^1.4.76", @@ -27336,7 +27618,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -27391,10 +27672,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001314", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001314.tgz", - "integrity": "sha512-0zaSO+TnCHtHJIbpLroX7nsD+vYuOVjl3uzFbJO1wMVbuveJA0RK2WcQA9ZUIOiO0/ArMiMgHJLxfEZhQiC0kw==", - "dev": true + "version": "1.0.30001320", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz", + "integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==" }, "caseless": { "version": "0.12.0", @@ -27436,7 +27716,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -27446,14 +27725,12 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -27737,7 +28014,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -27745,8 +28021,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-support": { "version": "1.1.3", @@ -28235,7 +28510,6 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, "requires": { "safe-buffer": "~5.1.1" } @@ -28275,7 +28549,6 @@ "version": "3.21.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", "integrity": "sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g==", - "dev": true, "requires": { "browserslist": "^4.19.1", "semver": "7.0.0" @@ -28284,8 +28557,7 @@ "semver": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" } } }, @@ -28480,7 +28752,6 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -28640,7 +28911,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -29224,8 +29494,7 @@ "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==", - "dev": true + "integrity": "sha512-o61+D/Lx7j/E0LIin/efOqeHpXhwi1TaQco9vUcRmr91m25SfZY6L5hWJDv/r+6kNjboFKgBw1LbfM0lbhuK6Q==" }, "emoji-regex": { "version": "6.1.1", @@ -29467,8 +29736,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-html": { "version": "1.0.3", @@ -29478,8 +29746,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.8.1", @@ -29660,12 +29927,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "globals": { "version": "13.12.1", "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", @@ -29675,12 +29936,6 @@ "type-fest": "^0.20.2" } }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -29966,8 +30221,7 @@ "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "etag": { "version": "1.8.1", @@ -30334,6 +30588,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": { @@ -30426,9 +30691,9 @@ } }, "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=", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, "fast-json-stable-stringify": { @@ -30869,8 +31134,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -30890,8 +31154,7 @@ "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, "get-caller-file": { "version": "2.0.5", @@ -30909,7 +31172,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -31393,8 +31655,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globals-docs": { "version": "2.4.1", @@ -32288,6 +32549,25 @@ } } }, + "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", @@ -32433,6 +32713,12 @@ "tmp": "^0.0.33" } }, + "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", @@ -32498,6 +32784,12 @@ "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=", + "dev": true + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -32656,6 +32948,15 @@ "string-width": "^2.1.1" } }, + "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" + } + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -32682,40 +32983,6 @@ } } }, - "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" - } - } - } - }, "gulp-if": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-3.0.0.tgz", @@ -33158,18 +33425,6 @@ "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true } } }, @@ -33183,7 +33438,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -33229,8 +33483,7 @@ "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", @@ -33330,6 +33583,16 @@ "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -33578,6 +33841,15 @@ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -33692,7 +33964,6 @@ "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==", - "dev": true, "requires": { "has": "^1.0.3" } @@ -34491,8 +34762,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "3.14.1", @@ -34521,8 +34791,7 @@ "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json-buffer": { "version": "3.0.1", @@ -34549,9 +34818,9 @@ "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=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -34570,7 +34839,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, "requires": { "minimist": "^1.2.5" } @@ -34677,15 +34945,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", @@ -35129,9 +35388,9 @@ "dev": true }, "live-connect-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.0.0.tgz", - "integrity": "sha512-Xhrj1JU5LoLjJuujjTlvDfc/n3Shzk2hPlYmLdCx/lsltFFVuCFa9uM8u5mcHlmOUKP5pu9I54bAITxZBMHoXg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-2.3.3.tgz", + "integrity": "sha512-WfY6v1jVutW/OGPvm0OaWAHc0MvB5MDdSuniZ+n9cpCltArWHTJtAsjWe8T+ACmdLAlZS9z7hzL3ntLnq+J0yQ==", "requires": { "tiny-hashes": "1.0.1" } @@ -35316,8 +35575,7 @@ "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, "lodash.defaults": { "version": "4.2.0", @@ -35534,6 +35792,15 @@ "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", "dev": true }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -36340,10 +36607,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "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", @@ -36613,8 +36879,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multipipe": { "version": "0.1.2", @@ -36809,8 +37074,7 @@ "node-releases": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", - "dev": true + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==" }, "nopt": { "version": "3.0.6", @@ -36996,8 +37260,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -37012,7 +37275,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", @@ -37222,6 +37484,12 @@ "readable-stream": "^2.0.1" } }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -37409,8 +37677,7 @@ "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-root": { "version": "0.1.1", @@ -37479,8 +37746,7 @@ "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "picomatch": { "version": "2.3.1", @@ -37662,6 +37928,12 @@ "integrity": "sha512-GA3TdL8szPK4AQ2YnOe/b+Y1jUFwmmGMMK/qbY7VcE3Z7FU8JstbKiKRzO6CIiAKPhTO8m01NoQ0V5f3jc4OGg==", "dev": true }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -38069,14 +38341,12 @@ "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "regenerate-unicode-properties": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz", "integrity": "sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==", - "dev": true, "requires": { "regenerate": "^1.4.2" } @@ -38090,7 +38360,6 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dev": true, "requires": { "@babel/runtime": "^7.8.4" } @@ -38125,7 +38394,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", "integrity": "sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw==", - "dev": true, "requires": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.0.1", @@ -38138,14 +38406,12 @@ "regjsgen": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz", - "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==", - "dev": true + "integrity": "sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==" }, "regjsparser": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz", "integrity": "sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==", - "dev": true, "requires": { "jsesc": "~0.5.0" }, @@ -38153,8 +38419,7 @@ "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" } } }, @@ -38405,7 +38670,6 @@ "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", - "dev": true, "requires": { "is-core-module": "^2.8.1", "path-parse": "^1.0.7", @@ -38553,8 +38817,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-json-parse": { "version": "1.0.1", @@ -38611,26 +38874,13 @@ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, "requires": {} - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true } } }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "semver-greatest-satisfied-range": { "version": "1.1.0", @@ -39169,8 +39419,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-js": { "version": "1.0.2", @@ -39765,8 +40014,7 @@ "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, "sver-compat": { "version": "1.5.0", @@ -39803,12 +40051,6 @@ "uri-js": "^4.2.2" } }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -39969,18 +40211,6 @@ "dev": true, "requires": {} }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -40115,12 +40345,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": { @@ -40136,8 +40366,7 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-object-path": { "version": "0.3.0", @@ -40254,6 +40483,12 @@ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, "trough": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", @@ -40346,6 +40581,13 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true, + "peer": true + }, "typescript-compare": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", @@ -40443,14 +40685,12 @@ "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" }, "unicode-match-property-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, "requires": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -40459,14 +40699,12 @@ "unicode-match-property-value-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true + "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==" }, "unicode-property-aliases-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==" }, "unified": { "version": "9.2.2", @@ -41136,18 +41374,6 @@ "dev": true, "requires": {} }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", diff --git a/package.json b/package.json index c812bc123f0..5a4a3abdb10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.18.0-pre", + "version": "7.8.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -34,20 +34,19 @@ "node": ">=8.9.0" }, "devDependencies": { - "@babel/core": "^7.16.7", "@babel/eslint-parser": "^7.16.5", - "@babel/preset-env": "^7.16.8", "@jsdevtools/coverage-istanbul-loader": "^3.0.3", "@wdio/browserstack-service": "^7.16.0", "@wdio/cli": "^7.5.2", "@wdio/concise-reporter": "^7.5.2", "@wdio/local-runner": "^7.5.2", "@wdio/mocha-framework": "^7.5.2", - "@wdio/spec-reporter": "^7.5.2", + "@wdio/spec-reporter": "^7.19.0", "@wdio/sync": "^7.5.2", - "ajv": "5.5.2", + "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", + "babel-register": "^6.26.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", @@ -69,8 +68,6 @@ "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-if": "^3.0.0", "gulp-js-escape": "^1.0.1", "gulp-replace": "^1.0.0", @@ -115,6 +112,8 @@ "yargs": "^1.3.1" }, "dependencies": { + "@babel/core": "^7.16.7", + "@babel/preset-env": "^7.16.8", "babel-plugin-transform-object-assign": "^6.22.0", "core-js": "^3.13.0", "core-js-pure": "^3.13.0", @@ -125,7 +124,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "2.0.0" + "live-connect-js": "2.3.3" }, "optionalDependencies": { "fsevents": "^2.3.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/AnalyticsAdapter.js b/src/AnalyticsAdapter.js index 6bed0d4cd6c..47f56dc054f 100644 --- a/src/AnalyticsAdapter.js +++ b/src/AnalyticsAdapter.js @@ -1,8 +1,11 @@ import CONSTANTS from './constants.json'; import { ajax } from './ajax.js'; import { logMessage, _each } from './utils.js'; +import * as events from './events.js' -const events = require('./events.js'); +export const _internal = { + ajax +}; const { EVENTS: { @@ -31,16 +34,17 @@ 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; if (analyticsType === ENDPOINT || BUNDLE) { _emptyQueue(); } - return { + return Object.defineProperties({ track: _track, enqueue: _enqueue, enableAnalytics: _enable, @@ -49,7 +53,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) { @@ -62,7 +70,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } } function _callEndpoint({ eventType, args, callback }) { - ajax(url, callback, JSON.stringify({ eventType, args })); + _internal.ajax(url, callback, JSON.stringify({ eventType, args })); } function _enqueue({ eventType, args }) { @@ -137,6 +145,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() { @@ -144,6 +153,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/Renderer.js b/src/Renderer.js index 830b979723c..f26a5a377c0 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -53,7 +53,7 @@ export function Renderer(options) { if (!isRendererPreferredFromAdUnit(adUnitCode)) { // 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); + 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() @@ -112,9 +112,18 @@ export function isRendererRequired(renderer) { * Render the bid returned by the adapter * @param {Object} renderer Renderer object installed by adapter * @param {Object} bid Bid response + * @param {Document} doc context document of bid */ -export function executeRenderer(renderer, bid) { - renderer.render(bid); +export function executeRenderer(renderer, bid, doc) { + let docContext = null; + if (renderer.config && renderer.config.documentResolver) { + docContext = renderer.config.documentResolver(bid, document, doc);// a user provided callback, which should return a Document, and expect the parameters; bid, sourceDocument, renderDocument + } + if (!docContext) { + docContext = document; + } + renderer.documentContext = docContext; + renderer.render(bid, renderer.documentContext); } function isRendererPreferredFromAdUnit(adUnitCode) { diff --git a/src/adapterManager.js b/src/adapterManager.js index 93eeba51cde..a707cc473d6 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'; @@ -31,15 +31,14 @@ import {includes, find} from './polyfill.js'; import { adunitCounter } from './adUnits.js'; import { getRefererInfo } from './refererDetection.js'; import {GdprConsentHandler, UspConsentHandler} from './consentHandler.js'; +import * as events from './events.js'; +import CONSTANTS from './constants.json'; export const PARTITIONS = { CLIENT: 'client', SERVER: 'server' } -var CONSTANTS = require('./constants.json'); -var events = require('./events.js'); - let adapterManager = {}; let _bidderRegistry = adapterManager.bidderRegistry = {}; @@ -69,8 +68,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src}) { 'nativeParams', 'ortb2Imp', 'mediaType', - 'renderer', - 'storedAuctionResponse' + 'renderer' ])); const mediaTypes = bid.mediaTypes == null ? adUnit.mediaTypes : bid.mediaTypes @@ -207,13 +205,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); @@ -225,6 +225,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); @@ -233,7 +243,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, @@ -242,8 +252,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); } @@ -270,15 +280,15 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a 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}`); @@ -303,7 +313,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a 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; @@ -347,7 +357,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(); @@ -417,7 +427,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; } @@ -431,7 +441,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 { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 87bc7a45491..e798c63d753 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -14,6 +14,7 @@ import { ADPOD } from '../mediaTypes.js'; import { getHook, hook } from '../hook.js'; import { getCoreStorageManager } from '../storageManager.js'; import {auctionManager} from '../auctionManager.js'; +import { bidderSettings } from '../bidderSettings.js'; export const storage = getCoreStorageManager('bidderFactory'); @@ -235,6 +236,11 @@ export function newBidder(spec) { onBid: (bid) => { const bidRequest = bidRequestMap[bid.requestId]; if (bidRequest) { + bid.adapterCode = bidRequest.bidder; + if (isInvalidAlternateBidder(bid.bidderCode, bidRequest.bidder)) { + logWarn(`${bid.bidderCode} is not a registered partner or known bidder of ${bidRequest.bidder}, hence continuing without bid. If you wish to support this bidder, please mark allowAlternateBidderCodes as true in bidderSettings.`); + return; + } // creating a copy of original values as cpm and currency are modified later bid.originalCpm = bid.cpm; bid.originalCurrency = bid.currency; @@ -250,6 +256,17 @@ export function newBidder(spec) { } }); + function isInvalidAlternateBidder(responseBidder, requestBidder) { + let allowAlternateBidderCodes = bidderSettings.get(requestBidder, 'allowAlternateBidderCodes') || false; + let alternateBiddersList = bidderSettings.get(requestBidder, 'allowedAlternateBidderCodes'); + if (!!responseBidder && !!requestBidder && requestBidder !== responseBidder) { + if (!allowAlternateBidderCodes || (isArray(alternateBiddersList) && (alternateBiddersList[0] !== '*' && !alternateBiddersList.includes(responseBidder)))) { + return true; + } + } + return false; + } + function registerSyncs(responses, gdprConsent, uspConsent) { registerSyncInner(spec, responses, gdprConsent, uspConsent); } @@ -525,7 +542,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 563fe039b2f..28acec136f6 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,16 +1,23 @@ import {includes} from './polyfill.js'; import { logError, logWarn, insertElement } from './utils.js'; -const _requestCache = {}; +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', 'browsi', 'brandmetrics', - 'justtag' + 'justtag', + 'tncId', + 'akamaidap', + 'ftrackId', + 'inskin', + 'hadron', + 'medianet' ] /** @@ -18,9 +25,10 @@ const _approvedLoadExternalJSList = [ * Each unique URL will be loaded at most 1 time. * @param {string} url the url to load * @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 {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 */ -export function loadExternalScript(url, moduleCode, callback) { +export function loadExternalScript(url, moduleCode, callback, doc) { if (!moduleCode || !url) { logError('cannot load external script without url and moduleCode'); return; @@ -29,46 +37,60 @@ export function loadExternalScript(url, moduleCode, callback) { logError(`${moduleCode} not whitelisted for loading external JavaScript`); return; } + if (!doc) { + doc = document; // provide a "valid" key for the WeakMap + } // only load each asset once - if (_requestCache[url]) { + const storedCachedObject = getCacheObject(doc, url); + if (storedCachedObject) { if (callback && typeof callback === 'function') { - if (_requestCache[url].loaded) { + if (storedCachedObject.loaded) { // invokeCallbacks immediately callback(); } else { // queue the callback - _requestCache[url].callbacks.push(callback); + storedCachedObject.callbacks.push(callback); } } - return _requestCache[url].tag; + return storedCachedObject.tag; } - _requestCache[url] = { + const cachedDocObj = _requestCache.get(doc) || {}; + const cacheObject = { loaded: false, tag: null, callbacks: [] }; + cachedDocObj[url] = cacheObject; + _requestCache.set(doc, cachedDocObj); + if (callback && typeof callback === 'function') { - _requestCache[url].callbacks.push(callback); + cacheObject.callbacks.push(callback); } logWarn(`module ${moduleCode} is loading external JavaScript`); return requestResource(url, function () { - _requestCache[url].loaded = true; + cacheObject.loaded = true; try { - for (let i = 0; i < _requestCache[url].callbacks.length; i++) { - _requestCache[url].callbacks[i](); + for (let i = 0; i < cacheObject.callbacks.length; i++) { + cacheObject.callbacks[i](); } } catch (e) { logError('Error executing callback', 'adloader.js:loadExternalScript', e); } - }); + }, doc); - function requestResource(tagSrc, callback) { - var jptScript = document.createElement('script'); + function requestResource(tagSrc, callback, doc) { + if (!doc) { + doc = document; + } + var jptScript = doc.createElement('script'); jptScript.type = 'text/javascript'; jptScript.async = true; - _requestCache[url].tag = jptScript; + const cacheObject = getCacheObject(doc, url); + if (cacheObject) { + cacheObject.tag = jptScript; + } if (jptScript.readyState) { jptScript.onreadystatechange = function () { @@ -86,8 +108,15 @@ export function loadExternalScript(url, moduleCode, callback) { jptScript.src = tagSrc; // add the new script tag to the page - insertElement(jptScript); + insertElement(jptScript, doc); return jptScript; } + function getCacheObject(doc, url) { + const cachedDocObj = _requestCache.get(doc); + if (cachedDocObj && cachedDocObj[url]) { + return cachedDocObj[url]; + } + return null; // return new cache object? + } }; diff --git a/src/adserver.js b/src/adserver.js index 61af8862972..8d99b29c3ef 100644 --- a/src/adserver.js +++ b/src/adserver.js @@ -1,5 +1,6 @@ import { formatQS } from './utils.js'; import { targeting } from './targeting.js'; +import {hook} from './hook.js'; // Adserver parent class const AdServer = function(attr) { @@ -11,6 +12,7 @@ const AdServer = function(attr) { }; // DFP ad server +// TODO: this seems to be unused? export function dfpAdserver(options, urlComponents) { var adserver = new AdServer(options); adserver.urlComponents = urlComponents; @@ -53,3 +55,8 @@ export function dfpAdserver(options, urlComponents) { return adserver; }; + +/** + * return the GAM PPID, if available (eid for the userID configured with `userSync.ppidSource`) + */ +export const getPPID = hook('sync', () => undefined); diff --git a/src/auction.js b/src/auction.js index 7a1b92b3c31..a1d6261201f 100644 --- a/src/auction.js +++ b/src/auction.js @@ -73,13 +73,13 @@ import { OUTSTREAM } from './video.js'; import { VIDEO } from './mediaTypes.js'; import {auctionManager} from './auctionManager.js'; 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; -const adapterManager = require('./adapterManager.js').default; -const events = require('./events.js'); -const CONSTANTS = require('./constants.json'); - export const AUCTION_STARTED = 'started'; export const AUCTION_IN_PROGRESS = 'inProgress'; export const AUCTION_COMPLETED = 'completed'; @@ -94,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 * @@ -104,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; @@ -216,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) { @@ -273,7 +282,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a } } } - }, _timeout, onTimelyResponse); + }, _timeout, onTimelyResponse, ortb2Fragments); } }; @@ -325,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 { @@ -349,7 +358,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a getBidRequests: () => _bidderRequests, getBidsReceived: () => _bidsReceived, getNoBids: () => _noBids, - + getFPD: () => ortb2Fragments } } @@ -376,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) { @@ -390,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(); @@ -572,7 +581,8 @@ function getPreparedBidForAuction({adUnitCode, bid, auctionId}, {index = auction } if (renderer) { - bidObject.renderer = Renderer.install({ url: renderer.url }); + // be aware, an adapter could already have installed the bidder, in which case this overwrite's the existing adapter + bidObject.renderer = Renderer.install({ url: renderer.url, config: renderer.options });// rename options to config, to make it consistent? bidObject.renderer.setRender(renderer.render); } @@ -756,7 +766,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)); } @@ -847,13 +857,7 @@ function groupByPlacement(bidsByPlacement, bid) { function getTimedOutBids(bidderRequests, timelyBidders) { const timedOutBids = bidderRequests .map(bid => (bid.bids || []).filter(bid => !timelyBidders.has(bid.bidder))) - .reduce(flatten, []) - .map(bid => ({ - bidId: bid.bidId, - bidder: bid.bidder, - adUnitCode: bid.adUnitCode, - auctionId: bid.auctionId, - })); + .reduce(flatten, []); return timedOutBids; } diff --git a/src/auctionManager.js b/src/auctionManager.js index 31f82bb0a89..6d60f28946c 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -23,8 +23,7 @@ import { uniques, flatten, logWarn } from './utils.js'; import { newAuction, getStandardBidderSettings, AUCTION_COMPLETED } from './auction.js'; import {find} from './polyfill.js'; import {AuctionIndex} from './auctionIndex.js'; - -const CONSTANTS = require('./constants.json'); +import CONSTANTS from './constants.json'; /** * Creates new instance of auctionManager. There will only be one instance of auctionManager but @@ -88,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/bidderSettings.js b/src/bidderSettings.js index 343d35a89d2..b39bf480511 100644 --- a/src/bidderSettings.js +++ b/src/bidderSettings.js @@ -1,7 +1,6 @@ import {deepAccess, mergeDeep} from './utils.js'; import {getGlobal} from './prebidGlobal.js'; - -const CONSTANTS = require('./constants.json'); +import CONSTANTS from './constants.json'; export class ScopedSettings { constructor(getSettings, defaultScope) { diff --git a/src/config.js b/src/config.js index 3fadebd9d07..a50edc09221 100644 --- a/src/config.js +++ b/src/config.js @@ -18,12 +18,10 @@ import { mergeDeep, deepClone, getParameterByName, isPlainObject, logMessage, logWarn, logError, isArray, isStr, isBoolean, deepAccess, bind } from './utils.js'; - -const CONSTANTS = require('./constants.json'); +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; @@ -87,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; }, @@ -314,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 @@ -358,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 @@ -485,14 +380,13 @@ 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; + topicalConfig[topic] = config[topic] = option; }); callSubscribers(topicalConfig); @@ -520,6 +414,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 * @@ -533,8 +429,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') { @@ -542,6 +439,7 @@ export function newConfig() { // meaning it gets called for any config change callback = topic; topic = ALL_TOPICS; + options = listener || {}; } if (typeof callback !== 'function') { @@ -552,6 +450,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); @@ -585,14 +492,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; } }); }); @@ -670,7 +576,9 @@ export function newConfig() { getCurrentBidder, resetBidder, getConfig, + getAnyConfig, readConfig, + readAnyConfig, setConfig, mergeConfig, setDefaults, @@ -680,9 +588,6 @@ export function newConfig() { setBidderConfig, getBidderConfig, mergeBidderConfig, - convertAdUnitFpd, - getLegacyFpd, - getLegacyImpFpd }; } diff --git a/src/consentHandler.js b/src/consentHandler.js index 0df9e6fcb3b..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; @@ -55,10 +55,13 @@ export class ConsentHandler { * @returns a promise than resolves to the consent data, or null if no consent data is available */ get promise() { + if (this.#ready) { + 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/debugging.js b/src/debugging.js index 810cf4b432a..817ec66e320 100644 --- a/src/debugging.js +++ b/src/debugging.js @@ -1,165 +1,86 @@ 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' : ''}`); -} - -export function disableOverrides() { - onDisableOverrides.forEach((fn) => fn()); - logMessage('bidder overrides disabled'); +function loadScript(url) { + return new GreedyPromise((resolve) => { + loadExternalScript(url, 'debugging', resolve); + }); } -/** - * @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; +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; } - 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; +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 (Array.isArray(overrides.bids)) { - overrides.bids.forEach(function(overrideBid) { - if (!bidExcluded(overrideBid, bid.bidderCode, adUnitCode)) { - applyBidOverrides(overrideBid, bid, 'bidder'); - } - }); - } - - next(adUnitCode, bid); -} - -export function addBidderRequestsHook(next, bidderRequests) { - const overrides = this; - - const includedBidderRequests = bidderRequests.filter(function (bidderRequest) { - if (bidderExcluded(overrides.bidders, bidderRequest.bidderCode)) { - logWarn(`bidRequest '${bidderRequest.bidderCode}' excluded from auction by bidder overrides`); - return false; + 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 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 const saveDebuggingConfig = hook('sync', function (debugConfig, {sessionStorage = window.sessionStorage} = {}) { - if (!debugConfig.enabled) { - try { - sessionStorage.removeItem(OVERRIDE_KEY); - } catch (e) {} - } else { - try { - sessionStorage.setItem(OVERRIDE_KEY, JSON.stringify(debugConfig)); - } catch (e) {} + function disable() { + hook.getHooks({hook: waitForDebugging}).remove(); + enabled = false; } -}); - -export function getConfig(debugging, {sessionStorage = window.sessionStorage} = {}) { - saveDebuggingConfig(debugging, {sessionStorage}); - if (!debugging.enabled) { - disableOverrides(); - } else { - enableOverrides(debugging); + function reset() { + promise = null; + disable(); } + return {enable, disable, reset}; } -config.getConfig('debugging', ({debugging}) => getConfig(debugging)); +const ctl = debuggingControls(); +export const reset = ctl.reset; -export function sessionLoader(storage) { - let overrides; +export function loadSession({storage = window.sessionStorage, debugging = ctl} = {}) { + let config = null; try { - storage = storage || window.sessionStorage; - overrides = JSON.parse(storage.getItem(OVERRIDE_KEY)); - } catch (e) { - } - if (overrides) { - enableOverrides(overrides, true); + config = storage.getItem(DEBUG_KEY); + } catch (e) {} + 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', function ({debugging}) { + debugging?.enabled ? ctl.enable() : ctl.disable(); +}); diff --git a/src/events.js b/src/events.js index 148e7b3a2f1..e675ffef7a9 100644 --- a/src/events.js +++ b/src/events.js @@ -1,8 +1,9 @@ /** * events.js */ -var utils = require('./utils.js'); -var CONSTANTS = require('./constants.json'); +import * as utils from './utils.js' +import CONSTANTS from './constants.json'; + var slice = Array.prototype.slice; var push = Array.prototype.push; @@ -149,4 +150,6 @@ const _public = (function () { return _public; }()); +utils._setEventEmitter(_public.emit.bind(_public)); + export const {on, off, get, getEvents, emit} = _public; diff --git a/src/hook.js b/src/hook.js index 2c8e4c7a6e7..226c49daeae 100644 --- a/src/hook.js +++ b/src/hook.js @@ -1,10 +1,28 @@ - import funHooks from 'fun-hooks/no-eval/index.js'; +import {defer} from './utils/promise.js'; export let hook = funHooks({ ready: funHooks.SYNC | funHooks.ASYNC | funHooks.QUEUE }); +const readyCtl = defer(); +hook.ready = (() => { + const ready = hook.ready; + return function () { + try { + return ready.apply(hook, arguments); + } finally { + readyCtl.resolve(); + } + } +})(); + +/** + * A promise that resolves when hooks are ready. + * @type {Promise} + */ +export const ready = readyCtl.promise; + export const getHook = hook.get; export function setupBeforeHookFnOnce(baseFn, hookFn, priority = 15) { diff --git a/src/native.js b/src/native.js index ba5e3a62901..65ccaf208ee 100644 --- a/src/native.js +++ b/src/native.js @@ -1,8 +1,7 @@ import { deepAccess, getKeyByValue, insertHtmlIntoIframe, logError, triggerPixel } from './utils.js'; import {includes} from './polyfill.js'; import {auctionManager} from './auctionManager.js'; - -const CONSTANTS = require('./constants.json'); +import CONSTANTS from './constants.json'; export const nativeAdapters = []; diff --git a/src/prebid.js b/src/prebid.js index d3f5ec989f3..ab336af08b6 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -5,7 +5,7 @@ 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'; @@ -13,19 +13,18 @@ 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 { loadSession } from './debugging.js'; import {includes} from './polyfill.js'; import { adunitCounter } from './adUnits.js'; import { executeRenderer, isRendererRequired } from './Renderer.js'; import { createBid } from './bidfactory.js'; import { storageCallbacks } from './storageManager.js'; import { emitAdRenderSucceeded, emitAdRenderFail } from './adRendering.js'; -import { gdprDataHandler, uspDataHandler } from './adapterManager.js' +import {gdprDataHandler, getS2SBidderSet, uspDataHandler, default as adapterManager} from './adapterManager.js'; +import CONSTANTS from './constants.json'; +import * as events from './events.js' const $$PREBID_GLOBAL$$ = getGlobal(); -const CONSTANTS = require('./constants.json'); -const adapterManager = require('./adapterManager.js').default; -const events = require('./events.js'); const { triggerUserSyncs } = userSync; /* private variables */ @@ -37,7 +36,7 @@ const eventValidators = { }; // initialize existing debugging sessions if present -sessionLoader(); +loadSession(); /* Public vars */ $$PREBID_GLOBAL$$.bidderSettings = $$PREBID_GLOBAL$$.bidderSettings || {}; @@ -189,10 +188,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 = []; @@ -213,7 +215,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); } @@ -486,7 +488,7 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { insertElement(creativeComment, doc, 'html'); if (isRendererRequired(renderer)) { - executeRenderer(renderer, bid); + executeRenderer(renderer, bid, doc); reinjectNodeIfRemoved(creativeComment, doc, 'html'); emitAdRenderSucceeded({ doc, bid, id }); } else if ((doc === document && !inIframe()) || mediaType === 'video') { @@ -571,25 +573,21 @@ $$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 } = {}) { +$$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels, auctionId, ortb2 } = {}) { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); - adUnits = (adUnits && config.convertAdUnitFpd(isArray(adUnits) ? adUnits : [adUnits])) || $$PREBID_GLOBAL$$.adUnits; - + adUnits = adUnits || $$PREBID_GLOBAL$$.adUnits; + adUnits = (isArray(adUnits) ? adUnits : [adUnits]); 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'); - let _s2sConfigs = []; - const s2sBidders = []; - config.getConfig('s2sConfig', config => { - if (config && config.s2sConfig) { - _s2sConfigs = Array.isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; - } - }); - - _s2sConfigs.forEach(s2sConfig => { - s2sBidders.push(...s2sConfig.bidders); - }); - +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) { @@ -614,10 +612,13 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo const allBidders = adUnit.bids.map(bid => bid.bidder); const bidderRegistry = adapterManager.bidderRegistry; - const bidders = (s2sBidders) ? allBidders.filter(bidder => !includes(s2sBidders, bidder)) : allBidders; + const bidders = allBidders.filter(bidder => !s2sBidders.has(bidder)); 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(); @@ -650,7 +651,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) { @@ -659,7 +668,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); @@ -685,7 +694,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); }; @@ -924,56 +933,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..7eacb3c3fc0 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -18,10 +18,15 @@ const STALE_RENDER = constants.EVENTS.STALE_RENDER; 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); } diff --git a/src/storageManager.js b/src/storageManager.js index 30e8d4c8abb..e703e0774d3 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -26,12 +26,12 @@ 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; } function isValid(cb) { if (includes(moduleTypeWhiteList, moduleType)) { @@ -39,7 +39,7 @@ export function newStorageManager({gvlid, moduleName, bidderCode, moduleType} = valid: true } return cb(result); - } else if (isBidderDisallowed()) { + } else if (!isBidderAllowed()) { logInfo(`bidderSettings denied access to device storage for bidder '${bidderCode}'`); const result = {valid: false}; return cb(result); diff --git a/src/targeting.js b/src/targeting.js index 6a80ce10806..cb10b3f0121 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -10,8 +10,7 @@ import { ADPOD } from './mediaTypes.js'; import { hook } from './hook.js'; import { bidderSettings } from './bidderSettings.js'; import {includes, find} from './polyfill.js'; - -var CONSTANTS = require('./constants.json'); +import CONSTANTS from './constants.json'; var pbTargetingKeys = []; @@ -177,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); @@ -584,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; } @@ -624,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/utils.js b/src/utils.js index 1d91a4a35cf..b53475af912 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,10 +1,8 @@ -/* eslint-disable no-console */ import { config } from './config.js'; import clone from 'just-clone'; import {find, includes} from './polyfill.js'; - -const CONSTANTS = require('./constants.json'); - +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'; @@ -21,16 +19,18 @@ let consoleInfoExists = Boolean(consoleExists && window.console.info); let consoleWarnExists = Boolean(consoleExists && window.console.warn); let consoleErrorExists = Boolean(consoleExists && window.console.error); -const emitEvent = (function () { - // lazy load events to avoid circular import - let ev; - return function() { - if (ev == null) { - ev = require('./events.js'); - } - return ev.emit.apply(ev, arguments); +let eventEmitter; + +export function _setEventEmitter(emitFn) { + // called from events.js - this hoop is to avoid circular imports + eventEmitter = emitFn; +} + +function emitEvent(...args) { + if (eventEmitter != null) { + eventEmitter(...args); } -})(); +} // this allows stubbing of utility functions that are used internally by other utility functions export const internal = { @@ -259,18 +259,21 @@ export function getWindowLocation() { */ export function logMessage() { if (debugTurnedOn() && consoleLogExists) { + // eslint-disable-next-line no-console console.log.apply(console, decorateLog(arguments, 'MESSAGE:')); } } export function logInfo() { if (debugTurnedOn() && consoleInfoExists) { + // eslint-disable-next-line no-console console.info.apply(console, decorateLog(arguments, 'INFO:')); } } export function logWarn() { if (debugTurnedOn() && consoleWarnExists) { + // eslint-disable-next-line no-console console.warn.apply(console, decorateLog(arguments, 'WARNING:')); } emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments}); @@ -278,6 +281,7 @@ export function logWarn() { export function logError() { if (debugTurnedOn() && consoleErrorExists) { + // eslint-disable-next-line no-console console.error.apply(console, decorateLog(arguments, 'ERROR:')); } emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments}); @@ -484,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; @@ -514,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); @@ -922,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 @@ -1355,3 +1348,23 @@ export function cyrb53Hash(str, seed = 0) { h2 = imul(h2 ^ (h2 >>> 16), 2246822507) ^ imul(h1 ^ (h1 >>> 13), 3266489909); return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(); } + +/** + * returns a window object, which holds the provided document or null + * @param {Document} doc + * @returns {Window} + */ +export function getWindowFromDocument(doc) { + return (doc) ? doc.defaultView : null; +} + +/** + * returns the result of `JSON.parse(data)`, or undefined if that throws an error. + * @param data + * @returns {any} + */ +export function safeJSONParse(data) { + try { + return JSON.parse(data); + } catch (e) {} +} 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 new file mode 100644 index 00000000000..229056786d2 --- /dev/null +++ b/src/utils/promise.js @@ -0,0 +1,111 @@ +const SUCCESS = 0; +const FAIL = 1; + +/** + * A version of Promise that runs callbacks synchronously when it can (i.e. after it's been fulfilled or rejected). + */ +export class GreedyPromise extends 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); + }); + } + + 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) => { + resolveFn = resolve; + rejectFn = reject; + }), + resolve: invoker(resolveFn), + reject: invoker(rejectFn) + } +} diff --git a/test/fake-server/bundle.js b/test/fake-server/bundle.js new file mode 100644 index 00000000000..b0430458083 --- /dev/null +++ b/test/fake-server/bundle.js @@ -0,0 +1,41 @@ +const fs = require('fs'); +const path = require('path'); +const makeBundle = require('../../gulpfile.js'); +const argv = require('yargs').argv; +const host = argv.host || 'localhost'; +const port = argv.port || 4444; +const dev = argv.dev || false; + +const REPLACE = { + 'https://ib.adnxs.com/ut/v3/prebid': `http://${host}:${port}/appnexus` +}; + +const replaceStrings = (() => { + const rules = Object.entries(REPLACE).map(([orig, repl]) => { + return [new RegExp(orig, 'g'), repl]; + }); + return function(text) { + return rules.reduce((text, [pat, repl]) => text.replace(pat, repl), text); + } +})(); + +const getBundle = (() => { + const cache = {}; + return function (modules = []) { + modules = Array.isArray(modules) ? [...modules] : [modules]; + modules.sort(); + const key = modules.join(','); + if (!cache.hasOwnProperty(key)) { + cache[key] = makeBundle(modules, dev).then(replaceStrings); + } + return cache[key]; + } +})(); + +module.exports = function (req, res, next) { + res.type('text/javascript'); + getBundle(req.query.modules).then((bundle) => { + res.write(bundle); + next(); + }).catch(next); +} diff --git a/test/fake-server/fake-responder.js b/test/fake-server/fake-responder.js index c884b00ca9c..a44d02260e7 100644 --- a/test/fake-server/fake-responder.js +++ b/test/fake-server/fake-responder.js @@ -6,9 +6,6 @@ const path = require('path'); // path to the fixture directory const fixturesPath = path.join(__dirname, 'fixtures'); -// An object storing 'Request-Response' pairs. -let REQ_RES_MAP = generateFixtures(fixturesPath); - /** * Matches 'req.body' with the responseBody pair * @param {object} requestBody - `req.body` of incoming request hitting middleware 'fakeResponder'. @@ -16,8 +13,8 @@ let REQ_RES_MAP = generateFixtures(fixturesPath); */ const matchResponse = function (requestBody) { let actualUuids = []; - - const requestResponsePairs = Object.keys(REQ_RES_MAP).map(testName => REQ_RES_MAP[testName]); + let reqResMap = generateFixtures(fixturesPath); + const requestResponsePairs = Object.keys(reqResMap).map(testName => reqResMap[testName]); // delete 'uuid' property requestBody.tags.forEach(body => { @@ -38,8 +35,22 @@ const matchResponse = function (requestBody) { requestResponsePairs .forEach(reqRes => { reqRes.request.httpRequest && reqRes.request.httpRequest.body.tags.forEach(body => body.uuid && delete body.uuid) }); + const match = requestResponsePairs.filter(reqRes => reqRes.request.httpRequest && deepEqual(reqRes.request.httpRequest.body.tags, requestBody.tags)); + + try { + if (match.length === 0) { + throw new Error('No mock response found'); + } else if (match.length > 1) { + throw new Error('More than one mock response found') + } + } catch (e) { + console.error(e); + console.error('Tags:', JSON.stringify(requestBody.tags, null, 2)); + throw e; + } + // match the 'actual' requestBody with the 'expected' requestBody and find the 'responseBody' - const responseBody = requestResponsePairs.filter(reqRes => reqRes.request.httpRequest && deepEqual(reqRes.request.httpRequest.body.tags, requestBody.tags))[0].response.httpResponse.body; + const responseBody = match[0].response.httpResponse.body; // ENABLE THE FOLLOWING CODE FOR TROUBLE-SHOOTING FAKED REQUESTS; COMMENT AGAIN WHEN DONE // console.log('value found for responseBody:', responseBody); diff --git a/test/fake-server/fixtures/basic-banner/request.json b/test/fake-server/fixtures/basic-banner/request.json index ea85b5a6842..6b355cd24c0 100644 --- a/test/fake-server/fixtures/basic-banner/request.json +++ b/test/fake-server/fixtures/basic-banner/request.json @@ -58,4 +58,4 @@ "user": {} } } -} \ No newline at end of file +} diff --git a/test/fake-server/fixtures/basic-outstream/request.json b/test/fake-server/fixtures/basic-outstream/request.json index 611a518fc2d..e9f3302ab4c 100644 --- a/test/fake-server/fixtures/basic-outstream/request.json +++ b/test/fake-server/fixtures/basic-outstream/request.json @@ -20,7 +20,7 @@ "disable_psa": true, "video": { "skippable": true, - "playback_method": ["auto_play_sound_off"] + "playback_method": 2 }, "hb_source": 1 }, { @@ -40,11 +40,11 @@ "disable_psa": true, "video": { "skippable": true, - "playback_method": ["auto_play_sound_off"] + "playback_method": 2 }, "hb_source": 1 }], "user": {} } } -} \ No newline at end of file +} diff --git a/test/fake-server/index.js b/test/fake-server/index.js index 752648c6746..e93bcfd465f 100644 --- a/test/fake-server/index.js +++ b/test/fake-server/index.js @@ -5,8 +5,9 @@ const morgan = require('morgan'); const bodyParser = require('body-parser'); const argv = require('yargs').argv; const fakeResponder = require('./fake-responder.js'); +const bundleMaker = require('./bundle.js'); -const PORT = argv.port || '3000'; +const PORT = argv.port || '4444'; // Initialize express app const app = express(); @@ -24,7 +25,11 @@ app.use(function(req, res, next) { next(); }); -app.post('/', fakeResponder, (req, res) => { +app.get('/bundle', bundleMaker, (req, res) => { + res.send(); +}); + +app.post('/appnexus', fakeResponder, (req, res) => { res.send(); }); diff --git a/test/helpers/consentData.js b/test/helpers/consentData.js new file mode 100644 index 00000000000..b59388d67f2 --- /dev/null +++ b/test/helpers/consentData.js @@ -0,0 +1,7 @@ +import {gdprDataHandler} from 'src/adapterManager.js'; +import {GreedyPromise} from '../../src/utils/promise.js'; + +export function mockGdprConsent(sandbox, getConsentData = () => null) { + sandbox.stub(gdprDataHandler, 'promise').get(() => GreedyPromise.resolve(getConsentData())); + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(getConsentData) +} 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/helpers/testing-utils.js b/test/helpers/testing-utils.js index 76e2b652a79..81f22ca471d 100644 --- a/test/helpers/testing-utils.js +++ b/test/helpers/testing-utils.js @@ -1,13 +1,51 @@ /* eslint-disable no-console */ -module.exports = { +const {expect} = require('chai'); +const utils = { host: (process.env.TEST_SERVER_HOST) ? process.env.TEST_SERVER_HOST : 'localhost', protocol: (process.env.TEST_SERVER_PROTOCOL) ? 'https' : 'http', + testPageURL: function(name) { + return `${utils.protocol}://${utils.host}:9999/test/pages/${name}` + }, waitForElement: function(elementRef, time = 2000) { let element = $(elementRef); element.waitForExist({timeout: time}); }, - switchFrame: function(frameRef, frameName) { + switchFrame: function(frameRef) { let iframe = $(frameRef); browser.switchToFrame(iframe); + }, + loadAndWaitForElement(url, selector, pause = 3000, timeout = 2000, retries = 3, attempt = 1) { + browser.url(url); + browser.pause(pause); + if (selector != null) { + try { + utils.waitForElement(selector, timeout); + } catch (e) { + if (attempt < retries) { + utils.loadAndWaitForElement(url, selector, pause, timeout, retries, attempt + 1); + } + } + } + }, + setupTest({url, waitFor, expectGAMCreative = null, pause = 3000, timeout = 2000, retries = 3}, name, fn) { + describe(name, function () { + this.retries(retries); + before(() => utils.loadAndWaitForElement(url, waitFor, pause, timeout, retries)); + fn.call(this); + if (expectGAMCreative) { + expectGAMCreative = expectGAMCreative === true ? waitFor : expectGAMCreative; + it(`should render GAM creative`, () => { + utils.switchFrame(expectGAMCreative); + const creative = [ + '> a > img', // banner + '> div[class="card"]' // native + ].map((child) => `body > div[class="GoogleActiveViewElement"] ${child}`) + .join(', '); + expect($(creative).isExisting()).to.be.true; + }); + } + }); } } + +module.exports = utils; diff --git a/test/mocks/analyticsStub.js b/test/mocks/analyticsStub.js new file mode 100644 index 00000000000..1023db882e8 --- /dev/null +++ b/test/mocks/analyticsStub.js @@ -0,0 +1,13 @@ +import {_internal} from '../../src/AnalyticsAdapter.js'; + +before(() => { + // stub out analytics networking to avoid random events polluting the global xhr mock + disableAjaxForAnalytics(); +}) + +export function disableAjaxForAnalytics() { + sinon.stub(_internal, 'ajax').callsFake(() => null); +} +export function enableAjaxForAnalytics() { + _internal.ajax.restore(); +} diff --git a/test/mocks/xhr.js b/test/mocks/xhr.js index 9fb8fe87fa0..424100f870c 100644 --- a/test/mocks/xhr.js +++ b/test/mocks/xhr.js @@ -1,3 +1,4 @@ +import {getUniqueIdentifierStr} from '../../src/utils.js'; export let server = sinon.createFakeServer(); export let xhr = global.XMLHttpRequest; @@ -7,3 +8,24 @@ beforeEach(function() { server = sinon.createFakeServer(); xhr = global.XMLHttpRequest; }); + +const bid = getUniqueIdentifierStr().substring(4); +let fid = 0; + +/* eslint-disable */ +afterEach(function () { + if (this?.currentTest?.state === 'failed') { + const prepend = (() => { + const preamble = `[Failure ${bid}-${fid++}]`; + return (s) => s.split('\n').map(s => `${preamble} ${s}`).join('\n'); + })(); + + + console.log(prepend(`XHR mock state after failure (for test '${this.currentTest.fullTitle()}'): ${server.requests.length} requests`)) + server.requests.forEach((req, i) => { + console.log(prepend(`Request #${i}:`)); + console.log(prepend(JSON.stringify(req, null, 2))); + }) + } +}); +/* eslint-enable */ diff --git a/test/pages/banner.html b/test/pages/banner.html index 75993cefb39..2e88d356647 100644 --- a/test/pages/banner.html +++ b/test/pages/banner.html @@ -7,7 +7,7 @@ Prebid.js Banner Example - + diff --git a/test/pages/bidderSettings.html b/test/pages/bidderSettings.html index 015ad3ca45f..205fc250be1 100644 --- a/test/pages/bidderSettings.html +++ b/test/pages/bidderSettings.html @@ -1,7 +1,7 @@ - + + + - + - +