diff --git a/.babelrc.js b/.babelrc.js index 74d26caf0fd..43303f59a8b 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -14,16 +14,7 @@ module.exports = { [ useLocal('@babel/preset-env'), { - "targets": { - "browsers": [ - "chrome >= 75", - "safari >=10", - "edge >= 70", - "ff >= 70", - "ie >= 11", - "ios >= 11" - ] - } + "useBuiltIns": "entry" } ] ], diff --git a/.circleci/config.yml b/.circleci/config.yml index a141b1ce4dd..404026d9446 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ # Check https://circleci.com/docs/2.0/language-javascript/ for more details # -aliases: +aliases: - &environment docker: # specify the version you desire here @@ -17,8 +17,9 @@ aliases: - &restore_dep_cache keys: - - v5.20.x-legacy-dependencies-{{ checksum "package.json" }} - - v5.20.x-legacy-dependencies- + - v1-dependencies-{{ checksum "package.json" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- - &save_dep_cache paths: @@ -51,7 +52,7 @@ aliases: - &unit_test_steps - checkout - restore_cache: *restore_dep_cache - - run: npm install + - run: npm ci - save_cache: *save_dep_cache - run: *install - run: *setup_browserstack @@ -71,7 +72,7 @@ jobs: build: <<: *environment steps: *unit_test_steps - + e2etest: <<: *environment steps: *endtoend_test_steps diff --git a/README.md b/README.md index be16e9e3547..bc0e64afa06 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ module.exports = { loader: 'babel-loader', // presets and plugins for Prebid.js must be manually specified separate from your other babel rule. // this can be accomplished by requiring prebid's .babelrc.js file (requires Babel 7 and Node v8.9.0+) + // as of Prebid 6, babelrc.js only targets modern browsers. One can change the targets and build for + // older browsers if they prefer, but integration tests on ie11 were removed in Prebid.js 6.0 options: require('prebid.js/.babelrc.js') } } @@ -272,7 +274,7 @@ As you make code changes, the bundles will be rebuilt and the page reloaded auto ## Contribute -Many SSPs, bidders, and publishers have contributed to this project. [Hundreds of bidders](https://github.com/prebid/Prebid.js/tree/master/src/adapters) are supported by Prebid.js. +Many SSPs, bidders, and publishers have contributed to this project. [Hundreds of bidders](https://github.com/prebid/Prebid.js/tree/master/modules) are supported by Prebid.js. For guidelines, see [Contributing](./CONTRIBUTING.md). @@ -314,7 +316,7 @@ For instructions on writing tests for Prebid.js, see [Testing Prebid.js](http:// ### Supported Browsers -Prebid.js is supported on IE11 and modern browsers. +Prebid.js is supported on IE11 and modern browsers until 5.x. 6.x+ transpiles to target >0.25%; not Opera Mini; not IE11. ### Governance Review our governance model [here](https://github.com/prebid/Prebid.js/tree/master/governance.md). diff --git a/browsers.json b/browsers.json index aad51ca6383..bd6bd5772d6 100644 --- a/browsers.json +++ b/browsers.json @@ -1,73 +1,49 @@ { - "bs_edge_17_windows_10": { + "bs_edge_latest_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "edge", - "browser_version": "17.0", + "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_edge_90_windows_10": { - "base": "BrowserStack", - "os_version": "10", - "browser": "edge", - "browser_version": "90.0", - "device": null, - "os": "Windows" - }, - "bs_ie_11_windows_10": { - "base": "BrowserStack", - "os_version": "10", - "browser": "ie", - "browser_version": "11.0", - "device": null, - "os": "Windows" - }, - "bs_chrome_90_windows_10": { + "bs_chrome_latest_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "90.0", + "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_chrome_79_windows_10": { + "bs_chrome_87_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "79.0", - "device": null, - "os": "Windows" - }, - "bs_firefox_88_windows_10": { - "base": "BrowserStack", - "os_version": "10", - "browser": "firefox", - "browser_version": "88.0", + "browser_version": "87.0", "device": null, "os": "Windows" }, - "bs_firefox_72_windows_10": { + "bs_firefox_latest_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "firefox", - "browser_version": "72.0", + "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_safari_14_mac_bigsur": { + "bs_safari_latest_mac_bigsur": { "base": "BrowserStack", "os_version": "Big Sur", "browser": "safari", - "browser_version": "14.0", + "browser_version": "latest", "device": null, "os": "OS X" }, - "bs_safari_12_mac_mojave": { + "bs_safari_15_catalina": { "base": "BrowserStack", - "os_version": "Mojave", + "os_version": "Catalina", "browser": "safari", - "browser_version": "12.0", + "browser_version": "13.1", "device": null, "os": "OS X" } diff --git a/gulpfile.js b/gulpfile.js index 8609177a8b9..6ecfee1b672 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -76,6 +76,7 @@ function lint(done) { 'modules/**/*.js', 'test/**/*.js', 'plugins/**/*.js', + '!plugins/**/node_modules/**', './*.js' ], { base: './' }) .pipe(gulpif(argv.nolintfix, eslint(), eslint({ fix: true }))) @@ -114,38 +115,6 @@ function viewReview(done) { viewReview.displayName = 'view-review'; -// Watch Task with Live Reload -function watch(done) { - var mainWatcher = gulp.watch([ - 'src/**/*.js', - 'modules/**/*.js', - 'test/spec/**/*.js', - '!test/spec/loaders/**/*.js' - ]); - var loaderWatcher = gulp.watch([ - 'loaders/**/*.js', - 'test/spec/loaders/**/*.js' - ]); - - connect.server({ - https: argv.https, - port: port, - host: FAKE_SERVER_HOST, - root: './', - livereload: true - }); - - mainWatcher.on('all', gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test))); - loaderWatcher.on('all', gulp.series(lint)); - done(); -}; - -function makeModuleList(modules) { - return modules.map(module => { - return '"' + module + '"' - }); -} - function makeDevpackPkg() { var cloned = _.cloneDeep(webpackConfig); cloned.devtool = 'source-map'; @@ -157,7 +126,6 @@ function makeDevpackPkg() { return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) - .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules))) .pipe(gulp.dest('build/dev')) .pipe(connect.reload()); } @@ -175,7 +143,6 @@ function makeWebpackPkg() { .pipe(helpers.nameModules(externalModules)) .pipe(webpackStream(cloned, webpack)) .pipe(terser()) - .pipe(replace(/('|")v\$prebid\.modulesList\$('|")/g, makeModuleList(externalModules))) .pipe(gulpif(file => file.basename === 'prebid-core.js', header(banner, { prebid: prebid }))) .pipe(gulp.dest('build/dist')); } @@ -254,60 +221,68 @@ function bundle(dev, moduleArr) { // If --browsers is given, browsers can be chosen explicitly. e.g. --browsers=chrome,firefox,ie9 // If --notest is given, it will immediately skip the test task (useful for developing changes with `gulp serve --notest`) -function test(done) { - if (argv.notest) { - done(); - } else if (argv.e2e) { - let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); - let wdioConf = path.join(__dirname, 'wdio.conf.js'); - let wdioOpts; - - if (argv.file) { - wdioOpts = [ - wdioConf, - `--spec`, - `${argv.file}` - ] - } else { - wdioOpts = [ - wdioConf - ]; - } +function testTaskMaker(options = {}) { + ['watch', 'e2e', 'file', 'browserstack', 'notest'].forEach(opt => { + options[opt] = options[opt] || argv[opt]; + }) - // 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}`); - }); + 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 + ]; + } - execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) - .then(stdout => { - // kill fake server - fakeServer.kill('SIGINT'); - done(); - process.exit(0); - }) - .catch(err => { - // kill fake server - fakeServer.kill('SIGINT'); - done(new Error(`Tests failed with error: ${err}`)); - process.exit(1); + // 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}`); }); - } else { - var karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch, argv.file); - var browserOverride = helpers.parseBrowserArgs(argv); - if (browserOverride.length > 0) { - karmaConf.browsers = browserOverride; - } + execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) + .then(stdout => { + // kill fake server + fakeServer.kill('SIGINT'); + done(); + process.exit(0); + }) + .catch(err => { + // kill fake server + fakeServer.kill('SIGINT'); + done(new Error(`Tests failed with error: ${err}`)); + process.exit(1); + }); + } else { + var karmaConf = karmaConfMaker(false, options.browserstack, options.watch, options.file); - new KarmaServer(karmaConf, newKarmaCallback(done)).start(); + var browserOverride = helpers.parseBrowserArgs(argv); + if (browserOverride.length > 0) { + karmaConf.browsers = browserOverride; + } + + new KarmaServer(karmaConf, newKarmaCallback(done)).start(); + } } } +const test = testTaskMaker(); + function newKarmaCallback(done) { return function (exitCode) { if (exitCode) { @@ -384,6 +359,35 @@ function startFakeServer() { }); } +// Watch Task with Live Reload +function watchTaskMaker(options = {}) { + if (options.livereload == null) { + options.livereload = true; + } + options.alsoWatch = options.alsoWatch || []; + + return function watch(done) { + var mainWatcher = gulp.watch([ + 'src/**/*.js', + 'modules/**/*.js', + ].concat(options.alsoWatch)); + + connect.server({ + https: argv.https, + port: port, + host: FAKE_SERVER_HOST, + root: './', + livereload: options.livereload + }); + + mainWatcher.on('all', options.task()); + done(); + } +} + +const watch = watchTaskMaker({alsoWatch: ['test/**/*.js'], task: () => gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test))}); +const watchFast = watchTaskMaker({livereload: false, task: () => gulp.series('build-bundle-dev')}); + // support tasks gulp.task(lint); gulp.task(watch); @@ -396,7 +400,8 @@ gulp.task('build-bundle-dev', gulp.series(makeDevpackPkg, gulpBundle.bind(null, gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg, gulpBundle.bind(null, false))); // public tasks (dependencies are needed for each task since they can be ran on their own) -gulp.task('test', gulp.series(clean, lint, test)); +gulp.task('test-only', test); +gulp.task('test', gulp.series(clean, lint, 'test-only')); gulp.task('test-coverage', gulp.series(clean, testCoverage)); gulp.task(viewCoverage); @@ -407,7 +412,8 @@ gulp.task('build', gulp.series(clean, 'build-bundle-prod')); 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', watch))); +gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast))); +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('default', gulp.series(clean, makeWebpackPkg)); diff --git a/integrationExamples/gpt/amp/creative.html b/integrationExamples/gpt/amp/creative.html index 86f669dd6b5..384b81107cc 100644 --- a/integrationExamples/gpt/amp/creative.html +++ b/integrationExamples/gpt/amp/creative.html @@ -1,38 +1,16 @@ + diff --git a/integrationExamples/gpt/weboramaRtdProvider_example.html b/integrationExamples/gpt/weboramaRtdProvider_example.html index 824d7a2f0c7..66e4a57d2a6 100644 --- a/integrationExamples/gpt/weboramaRtdProvider_example.html +++ b/integrationExamples/gpt/weboramaRtdProvider_example.html @@ -17,22 +17,31 @@ debug: true, realTimeData: { auctionDelay: 1000, - dataProviders: [ - { + dataProviders: [{ name: "weborama", waitForIt: true, params: { - weboCtxConf: { - setTargeting: true, - token: "to-be-defined", - targetURL: "https://prebid.org/", - defaultProfile: { - webo_ctx: ['moon'] - } - } - } - } - ] + weboCtxConf: { + token: "to-be-defined", // mandatory + targetURL: "https://prebid.org", // default is document.URL + setPrebidTargeting: true, // default + sendToBidders: true, // default + defaultProfile: { // optional + webo_ctx: ['moon'], + webo_ds: ['bar'] + } + }, + weboUserDataConf: { + setPrebidTargeting: true, // default + sendToBidders: true, // default + defaultProfile: { // optional + webo_cs: ['Red'], + webo_audiences: ['bam'] + }, + localStorageProfileKey: 'webo_wam2gam_entry' // default + } + } + }] } }); }); @@ -54,7 +63,7 @@ } }, bids: [{ - bidder: 'appnexus', + bidder: 'smartadserver', params: { placementId: 1 } diff --git a/karma.conf.maker.js b/karma.conf.maker.js index cf5999ba85e..be51947dae8 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -111,7 +111,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { var webpackConfig = newWebpackConfig(codeCoverage); var plugins = newPluginsArray(browserstack); - var files = file ? ['test/helpers/prebidGlobal.js', file] : ['test/test_index.js']; + var files = file ? ['test/test_deps.js', file] : ['test/test_index.js']; // This file opens the /debug.html tab automatically. // It has no real value unless you're running --watch, and intend to do some debugging in the browser. if (watchMode) { @@ -166,7 +166,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) { browserNoActivityTimeout: 3e5, // default 10000 captureTimeout: 3e5, // default 60000, browserDisconnectTolerance: 3, - concurrency: 5, + concurrency: 6, plugins: plugins } diff --git a/modules/.submodules.json b/modules/.submodules.json index ea3f556dbb4..2e77873dc78 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -35,7 +35,8 @@ "uid2IdSystem", "unifiedIdSystem", "verizonMediaIdSystem", - "zeotapIdPlusIdSystem" + "zeotapIdPlusIdSystem", + "adqueryIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 2bdbdd6414b..af67bb2bf48 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -1,11 +1,20 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { - deepAccess, uniques, isArray, getWindowTop, isGptPubadsDefined, isSlotMatchingAdUnitCode, logInfo, logWarn, - getWindowSelf + deepAccess, + uniques, + isArray, + getWindowTop, + isGptPubadsDefined, + isSlotMatchingAdUnitCode, + logInfo, + logWarn, + getWindowSelf, + mergeDeep, } from '../src/utils.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +// **************************** UTILS *************************** // const BIDDER_CODE = '33across'; 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'; @@ -42,6 +51,14 @@ const adapterState = { const NON_MEASURABLE = 'nm'; +function getTTXConfig() { + const ttxSettings = Object.assign({}, + config.getConfig('ttxSettings') + ); + + return ttxSettings; +} + // **************************** VALIDATION *************************** // function isBidRequestValid(bid) { return ( @@ -74,6 +91,7 @@ function _validateGUID(bid) { function _validateBanner(bid) { const banner = deepAccess(bid, 'mediaTypes.banner'); + // If there's no banner no need to validate against banner rules if (banner === undefined) { return true; @@ -140,91 +158,125 @@ function _validateVideo(bid) { // NOTE: With regards to gdrp consent data, the server will independently // infer the gdpr applicability therefore, setting the default value to false function buildRequests(bidRequests, bidderRequest) { + const { + ttxSettings, + gdprConsent, + uspConsent, + pageUrl + } = _buildRequestParams(bidRequests, bidderRequest); + + const groupedRequests = _buildRequestGroups(ttxSettings, bidRequests); + + const serverRequests = []; + + for (const key in groupedRequests) { + serverRequests.push( + _createServerRequest({ + bidRequests: groupedRequests[key], + gdprConsent, + uspConsent, + pageUrl, + ttxSettings + }) + ) + } + + return serverRequests; +} + +function _buildRequestParams(bidRequests, bidderRequest) { + const ttxSettings = getTTXConfig(); + const gdprConsent = Object.assign({ consentString: undefined, gdprApplies: false }, bidderRequest && bidderRequest.gdprConsent); const uspConsent = bidderRequest && bidderRequest.uspConsent; + const pageUrl = (bidderRequest && bidderRequest.refererInfo) ? (bidderRequest.refererInfo.referer) : (undefined); adapterState.uniqueSiteIds = bidRequests.map(req => req.params.siteId).filter(uniques); - return bidRequests.map(bidRequest => _createServerRequest( - { - bidRequest, - gdprConsent, - uspConsent, - pageUrl - }) - ); + return { + ttxSettings, + gdprConsent, + uspConsent, + pageUrl + } +} + +function _buildRequestGroups(ttxSettings, bidRequests) { + const bidRequestsComplete = bidRequests.map(_inferProduct); + const enableSRAMode = ttxSettings && ttxSettings.enableSRAMode; + const keyFunc = (enableSRAMode === true) ? _getSRAKey : _getMRAKey; + + return _groupBidRequests(bidRequestsComplete, keyFunc); +} + +function _groupBidRequests(bidRequests, keyFunc) { + const groupedRequests = {}; + + bidRequests.forEach((req) => { + const key = keyFunc(req); + + groupedRequests[key] = groupedRequests[key] || []; + groupedRequests[key].push(req); + }); + + return groupedRequests; +} + +function _getSRAKey(bidRequest) { + return `${bidRequest.params.siteId}:${bidRequest.params.productId}`; +} + +function _getMRAKey(bidRequest) { + return `${bidRequest.bidId}`; } // Infer the necessary data from valid bid for a minimal ttxRequest and create HTTP request -// NOTE: At this point, TTX only accepts request for a single impression -function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl}) { +function _createServerRequest({ bidRequests, gdprConsent = {}, uspConsent, pageUrl, ttxSettings }) { const ttxRequest = {}; - const params = bidRequest.params; + const { siteId, test } = bidRequests[0].params; /* * Infer data for the request payload */ - ttxRequest.imp = [{}]; + ttxRequest.imp = []; - if (deepAccess(bidRequest, 'mediaTypes.banner')) { - ttxRequest.imp[0].banner = { - ..._buildBannerORTB(bidRequest) - } - } - - if (deepAccess(bidRequest, 'mediaTypes.video')) { - ttxRequest.imp[0].video = _buildVideoORTB(bidRequest); - } - - ttxRequest.imp[0].ext = { - ttx: { - prod: _getProduct(bidRequest) - } - }; + bidRequests.forEach((req) => { + ttxRequest.imp.push(_buildImpORTB(req)); + }); - ttxRequest.site = { id: params.siteId }; + ttxRequest.site = { id: siteId }; if (pageUrl) { ttxRequest.site.page = pageUrl; } - // Go ahead send the bidId in request to 33exchange so it's kept track of in the bid response and - // therefore in ad targetting process - ttxRequest.id = bidRequest.bidId; + ttxRequest.id = bidRequests[0].auctionId; if (gdprConsent.consentString) { - ttxRequest.user = setExtension( - ttxRequest.user, - 'consent', - gdprConsent.consentString - ) + ttxRequest.user = setExtensions(ttxRequest.user, { + 'consent': gdprConsent.consentString + }); } - if (Array.isArray(bidRequest.userIdAsEids) && bidRequest.userIdAsEids.length > 0) { - ttxRequest.user = setExtension( - ttxRequest.user, - 'eids', - bidRequest.userIdAsEids - ) + if (Array.isArray(bidRequests[0].userIdAsEids) && bidRequests[0].userIdAsEids.length > 0) { + ttxRequest.user = setExtensions(ttxRequest.user, { + 'eids': bidRequests[0].userIdAsEids + }); } - ttxRequest.regs = setExtension( - ttxRequest.regs, - 'gdpr', - Number(gdprConsent.gdprApplies) - ); + ttxRequest.regs = setExtensions(ttxRequest.regs, { + 'gdpr': Number(gdprConsent.gdprApplies) + }); if (uspConsent) { - ttxRequest.regs = setExtension( - ttxRequest.regs, - 'us_privacy', - uspConsent - ) + ttxRequest.regs = setExtensions(ttxRequest.regs, { + 'us_privacy': uspConsent + }); } ttxRequest.ext = { @@ -237,16 +289,14 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl } }; - if (bidRequest.schain) { - ttxRequest.source = setExtension( - ttxRequest.source, - 'schain', - bidRequest.schain - ) + if (bidRequests[0].schain) { + ttxRequest.source = setExtensions(ttxRequest.source, { + 'schain': bidRequests[0].schain + }); } // Finally, set the openRTB 'test' param if this is to be a test bid - if (params.test === 1) { + if (test === 1) { ttxRequest.test = 1; } @@ -259,8 +309,7 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl }; // Allow the ability to configure the HB endpoint for testing purposes. - const ttxSettings = config.getConfig('ttxSettings'); - const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${params.siteId}`; + const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${siteId}`; // Return the server request return { @@ -272,14 +321,36 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl } // BUILD REQUESTS: SET EXTENSIONS -function setExtension(obj = {}, key, value) { - return Object.assign({}, obj, { - ext: Object.assign({}, obj.ext, { - [key]: value - }) +function setExtensions(obj = {}, extFields) { + return mergeDeep({}, obj, { + 'ext': extFields }); } +// BUILD REQUESTS: IMP +function _buildImpORTB(bidRequest) { + const imp = { + id: bidRequest.bidId, + ext: { + ttx: { + prod: deepAccess(bidRequest, 'params.productId') + } + } + }; + + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + imp.banner = { + ..._buildBannerORTB(bidRequest) + } + } + + if (deepAccess(bidRequest, 'mediaTypes.video')) { + imp.video = _buildVideoORTB(bidRequest); + } + + return imp; +} + // BUILD REQUESTS: SIZE INFERENCE function _transformSizes(sizes) { if (isArray(sizes) && sizes.length === 2 && !isArray(sizes[0])) { @@ -297,6 +368,14 @@ function _getSize(size) { } // BUILD REQUESTS: PRODUCT INFERENCE +function _inferProduct(bidRequest) { + return mergeDeep({}, bidRequest, { + params: { + productId: _getProduct(bidRequest) + } + }); +} + function _getProduct(bidRequest) { const { params, mediaTypes } = bidRequest; @@ -367,7 +446,7 @@ function _buildVideoORTB(bidRequest) { const video = {} - const {w, h} = _getSize(videoParams.playerSize[0]); + const { w, h } = _getSize(videoParams.playerSize[0]); video.w = w; video.h = h; @@ -388,11 +467,11 @@ function _buildVideoORTB(bidRequest) { if (product === PRODUCT.INSTREAM) { video.startdelay = video.startdelay || 0; video.placement = 1; - }; + } // bidfloors if (typeof bidRequest.getFloor === 'function') { - const bidfloors = _getBidFloors(bidRequest, {w: video.w, h: video.h}, VIDEO); + const bidfloors = _getBidFloors(bidRequest, { w: video.w, h: video.h }, VIDEO); if (bidfloors) { Object.assign(video, { @@ -404,6 +483,7 @@ function _buildVideoORTB(bidRequest) { }); } } + return video; } @@ -556,54 +636,61 @@ function _isIframe() { } // **************************** INTERPRET RESPONSE ******************************** // -// NOTE: At this point, the response from 33exchange will only ever contain one bid -// i.e. the highest bid function interpretResponse(serverResponse, bidRequest) { - const bidResponses = []; - - // If there are bids, look at the first bid of the first seatbid (see NOTE above for assumption about ttx) - if (serverResponse.body.seatbid.length > 0 && serverResponse.body.seatbid[0].bid.length > 0) { - bidResponses.push(_createBidResponse(serverResponse.body)); - } - - return bidResponses; + const { seatbid, cur = 'USD' } = serverResponse.body; + + if (!isArray(seatbid)) { + return []; + } + + // Pick seats with valid bids and convert them into an Array of responses + // in format expected by Prebid Core + return seatbid + .filter((seat) => ( + isArray(seat.bid) && + seat.bid.length > 0 + )) + .reduce((acc, seat) => { + return acc.concat( + seat.bid.map((bid) => _createBidResponse(bid, cur)) + ); + }, []); } -// All this assumes that only one bid is ever returned by ttx -function _createBidResponse(response) { +function _createBidResponse(bid, cur) { const isADomainPresent = - response.seatbid[0].bid[0].adomain && response.seatbid[0].bid[0].adomain.length; - const bid = { - requestId: response.id, + bid.adomain && bid.adomain.length; + const bidResponse = { + requestId: bid.impid, bidderCode: BIDDER_CODE, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ad: response.seatbid[0].bid[0].adm, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - mediaType: deepAccess(response.seatbid[0].bid[0], 'ext.ttx.mediaType', BANNER), - currency: response.cur, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: bid.ttl || 60, + creativeId: bid.crid, + mediaType: deepAccess(bid, 'ext.ttx.mediaType', BANNER), + currency: cur, netRevenue: true } if (isADomainPresent) { - bid.meta = { - advertiserDomains: response.seatbid[0].bid[0].adomain + bidResponse.meta = { + advertiserDomains: bid.adomain }; } - if (bid.mediaType === VIDEO) { - const vastType = deepAccess(response.seatbid[0].bid[0], 'ext.ttx.vastType', 'xml'); + if (bidResponse.mediaType === VIDEO) { + const vastType = deepAccess(bid, 'ext.ttx.vastType', 'xml'); if (vastType === 'xml') { - bid.vastXml = bid.ad; + bidResponse.vastXml = bidResponse.ad; } else { - bid.vastUrl = bid.ad; + bidResponse.vastUrl = bidResponse.ad; } } - return bid; + return bidResponse; } // **************************** USER SYNC *************************** // diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 264cf5f9fcb..b000772f214 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -308,7 +308,7 @@ function getElementFromTopWindow(element, currentWindow) { function autoDetectAdUnitElementIdFromGpt(adUnitCode) { const autoDetectedAdUnit = getGptSlotInfoForAdUnitCode(adUnitCode); - if (autoDetectedAdUnit && autoDetectedAdUnit.divId) { + if (autoDetectedAdUnit.divId) { return autoDetectedAdUnit.divId; } }; diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js index ca4795c574f..1b93d4fe1c6 100644 --- a/modules/adbookpspBidAdapter.js +++ b/modules/adbookpspBidAdapter.js @@ -363,7 +363,7 @@ function impBidsToPrebidBids( } const impToPrebidBid = - (bidderRequestBody, bidResponseCurrency, referrer, targetingMap) => (bid) => { + (bidderRequestBody, bidResponseCurrency, referrer, targetingMap) => (bid, bidIndex) => { try { const bidRequest = findBidRequest(bidderRequestBody, bid); @@ -377,7 +377,7 @@ const impToPrebidBid = let prebidBid = { ad: bid.adm, adId: bid.adid, - adserverTargeting: targetingMap[bid.impid], + adserverTargeting: targetingMap[bidIndex], adUnitCode: bidRequest.tagid, bidderRequestId: bidderRequestBody.id, bidId: bid.id, @@ -408,6 +408,9 @@ const impToPrebidBid = }; } + if (deepAccess(bid, 'ext.pa_win') === true) { + prebidBid.auctionWinner = true; + } return prebidBid; } catch (error) { logError(`${BIDDER_CODE}: Error while building bid`, error); @@ -429,29 +432,43 @@ function buildTargetingMap(bids) { const values = impIds.reduce((result, id) => { result[id] = { lineItemIds: [], + orderIds: [], dealIds: [], adIds: [], + adAndOrderIndexes: [] }; return result; }, {}); - bids.forEach((bid) => { - values[bid.impid].lineItemIds.push(bid.ext.liid); - values[bid.impid].dealIds.push(bid.dealid); - values[bid.impid].adIds.push(bid.adid); + bids.forEach((bid, bidIndex) => { + let impId = bid.impid; + values[impId].lineItemIds.push(bid.ext.liid); + values[impId].dealIds.push(bid.dealid); + values[impId].adIds.push(bid.adid); + + if (deepAccess(bid, 'ext.ordid')) { + values[impId].orderIds.push(bid.ext.ordid); + bid.ext.ordid.split(TARGETING_VALUE_SEPARATOR).forEach((ordid, ordIndex) => { + let adIdIndex = values[impId].adIds.indexOf(bid.adid); + values[impId].adAndOrderIndexes.push(adIdIndex + '_' + ordIndex) + }) + } }); const targetingMap = {}; - for (const id of impIds) { - targetingMap[id] = { + bids.forEach((bid, bidIndex) => { + let id = bid.impid; + + targetingMap[bidIndex] = { hb_liid_adbookpsp: values[id].lineItemIds.join(TARGETING_VALUE_SEPARATOR), hb_deal_adbookpsp: values[id].dealIds.join(TARGETING_VALUE_SEPARATOR), + hb_ad_ord_adbookpsp: values[id].adAndOrderIndexes.join(TARGETING_VALUE_SEPARATOR), hb_adid_c_adbookpsp: values[id].adIds.join(TARGETING_VALUE_SEPARATOR), + hb_ordid_adbookpsp: values[id].orderIds.join(TARGETING_VALUE_SEPARATOR), }; - } - + }) return targetingMap; } diff --git a/modules/addefendBidAdapter.js b/modules/addefendBidAdapter.js index 18cafe829b5..dcc453ef35a 100644 --- a/modules/addefendBidAdapter.js +++ b/modules/addefendBidAdapter.js @@ -19,6 +19,7 @@ export const spec = { v: $$PREBID_GLOBAL$$.version, auctionId: false, 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, bids: [], diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js index c94a4e35efd..6a8c98650c0 100644 --- a/modules/adhashBidAdapter.js +++ b/modules/adhashBidAdapter.js @@ -6,7 +6,7 @@ const VERSION = '1.0'; export const spec = { code: 'adhash', - url: 'https://bidder.adhash.org/rtb?version=' + VERSION + '&prebid=true', + url: 'https://bidder.adhash.com/rtb?version=' + VERSION + '&prebid=true', supportedMediaTypes: [ BANNER ], isBidRequestValid: (bid) => { @@ -37,7 +37,7 @@ export const spec = { var size = validBidRequests[i].sizes[index].join('x'); bidRequests.push({ method: 'POST', - url: url, + url: url + '&publisher=' + validBidRequests[i].params.publisherId, bidRequest: validBidRequests[i], data: { timezone: new Date().getTimezoneOffset() / 60, @@ -87,7 +87,7 @@ export const spec = { cpm: responseBody.creatives[0].costEUR, ad: `
- + `, width: request.bidRequest.sizes[0][0], height: request.bidRequest.sizes[0][1], diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 88f3e0e0e4f..145b5605bc2 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -2,6 +2,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; const BIDDER_CODE = 'adhese'; const GVLID = 553; @@ -20,11 +21,15 @@ export const spec = { if (validBidRequests.length === 0) { return null; } + const { gdprConsent, refererInfo } = bidderRequest; + const adheseConfig = config.getConfig('adhese'); const gdprParams = (gdprConsent && gdprConsent.consentString) ? { xt: [gdprConsent.consentString] } : {}; const refererParams = (refererInfo && refererInfo.referer) ? { xf: [base64urlEncode(refererInfo.referer)] } : {}; - const commonParams = { ...gdprParams, ...refererParams }; + const globalCustomParams = (adheseConfig && adheseConfig.globalTargets) ? cleanTargets(adheseConfig.globalTargets) : {}; + const commonParams = { ...globalCustomParams, ...gdprParams, ...refererParams }; + const vastContentAsUrl = !(adheseConfig && adheseConfig.vastContentAsUrl == false); const slots = validBidRequests.map(bid => ({ slotname: bidToSlotName(bid), @@ -34,7 +39,7 @@ export const spec = { const payload = { slots: slots, parameters: commonParams, - vastContentAsUrl: true, + vastContentAsUrl: vastContentAsUrl, user: { ext: { eids: getEids(validBidRequests), diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 11c8b464a7e..c23eca2f96a 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -1,6 +1,6 @@ import { isStr, isArray, isPlainObject, deepSetValue, isNumber, deepAccess, getAdUnitSizes, parseGPTSingleSizeArrayToRtbSize, - cleanObj, contains, getDNT, parseUrl, createTrackPixelHtml, _each, isArrayOfNums + cleanObj, contains, getDNT, parseUrl, createTrackPixelHtml, _each, isArrayOfNums, mergeDeep, isEmpty, inIframe } from '../src/utils.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -75,8 +75,10 @@ export const spec = { {code: 'denakop'}, {code: 'rtbanalytica'}, {code: 'unibots'}, + {code: 'catapultx'}, {code: 'ergadx'}, - {code: 'turktelekom'} + {code: 'turktelekom'}, + {code: 'felixads'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -102,17 +104,16 @@ export const spec = { * @returns {ServerRequest[]} */ buildRequests: function (bidRequests, bidderRequest) { - let impDispatch = dispatchImps(bidRequests, bidderRequest.refererInfo); + let impGroups = groupImpressionsByHostZone(bidRequests, bidderRequest.refererInfo); let requests = []; let schain = bidRequests[0].schain; - Object.keys(impDispatch).forEach(host => { - Object.keys(impDispatch[host]).forEach(zoneId => { - const request = buildRtbRequest(impDispatch[host][zoneId], bidderRequest, schain); - requests.push({ - method: 'POST', - url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`, - data: JSON.stringify(request) - }); + _each(impGroups, impGroup => { + let {host, zoneId, imps} = impGroup; + const request = buildRtbRequest(imps, bidderRequest, schain); + requests.push({ + method: 'POST', + url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`, + data: JSON.stringify(request) }); }); return requests; @@ -208,17 +209,19 @@ registerBidder(spec); * @param bidRequests {BidRequest[]} * @param refererInfo {refererInfo} */ -function dispatchImps(bidRequests, refererInfo) { +function groupImpressionsByHostZone(bidRequests, refererInfo) { let secure = (refererInfo && refererInfo.referer.indexOf('https:') === 0); - return bidRequests.map(bidRequest => buildImp(bidRequest, secure)) - .reduce((acc, curr, index) => { - let bidRequest = bidRequests[index]; - let {zoneId, host} = bidRequest.params; - acc[host] = acc[host] || {}; - acc[host][zoneId] = acc[host][zoneId] || []; - acc[host][zoneId].push(curr); - return acc; - }, {}); + return Object.values( + bidRequests.map(bidRequest => buildImp(bidRequest, secure)) + .reduce((acc, curr, index) => { + let bidRequest = bidRequests[index]; + let {zoneId, host} = bidRequest.params; + let key = `${host}_${zoneId}`; + acc[key] = acc[key] || {host: host, zoneId: zoneId, imps: []}; + acc[key].imps.push(curr); + return acc; + }, {}) + ); } function getBidFloor(bid, mediaType, sizes) { @@ -364,57 +367,142 @@ function getAllowedSyncMethod(bidderCode) { } /** - * Builds complete rtb request - * @param imps {Object} Collection of rtb impressions - * @param bidderRequest {BidderRequest} - * @param schain {Object=} Supply chain config - * @return {Object} Complete rtb request + * Create device object from fpd and host-collected data + * @param fpd {Object} + * @returns {{device: Object}} */ -function buildRtbRequest(imps, bidderRequest, schain) { - let {bidderCode, gdprConsent, auctionId, refererInfo, timeout, uspConsent} = bidderRequest; - let coppa = config.getConfig('coppa'); - let req = { - 'id': auctionId, - 'imp': imps, - 'site': createSite(refererInfo), - 'at': 1, - 'device': { - 'ip': 'caller', - 'ipv6': 'caller', - 'ua': 'caller', - 'js': 1, - 'language': getLanguage() - }, - 'tmax': parseInt(timeout) - }; +function makeDevice(fpd) { + let device = mergeDeep({ + 'ip': 'caller', + 'ipv6': 'caller', + 'ua': 'caller', + 'js': 1, + 'language': getLanguage() + }, fpd.device || {}); if (getDNT()) { - req.device.dnt = 1; + device.dnt = 1; + } + return {device: device}; +} + +/** + * Create site or app description object + * @param bidderRequest {BidderRequest} + * @param fpd {Object} + * @returns {{site: Object}|{app: Object}} + */ +function makeSiteOrApp(bidderRequest, fpd) { + let {refererInfo} = bidderRequest; + let appConfig = config.getConfig('app'); + if (isEmpty(appConfig)) { + return {site: createSite(refererInfo, fpd)} + } else { + return {app: appConfig}; } +} + +/** + * Create user description object + * @param bidderRequest {BidderRequest} + * @param fpd {Object} + * @returns {{user: Object} | undefined} + */ +function makeUser(bidderRequest, fpd) { + let {gdprConsent} = bidderRequest; + let user = fpd.user || {}; + if (gdprConsent && gdprConsent.consentString !== undefined) { + deepSetValue(user, 'ext.consent', gdprConsent.consentString); + } + let eids = getExtendedUserIds(bidderRequest); + if (eids) { + deepSetValue(user, 'ext.eids', eids); + } + if (!isEmpty(user)) { return {user: user}; } +} + +/** + * Create privacy regulations object + * @param bidderRequest {BidderRequest} + * @returns {{regs: Object} | undefined} + */ +function makeRegulations(bidderRequest) { + let {gdprConsent, uspConsent} = bidderRequest; + let regs = {}; if (gdprConsent) { if (gdprConsent.gdprApplies !== undefined) { - deepSetValue(req, 'regs.ext.gdpr', ~~gdprConsent.gdprApplies); - } - if (gdprConsent.consentString !== undefined) { - deepSetValue(req, 'user.ext.consent', gdprConsent.consentString); + deepSetValue(regs, 'regs.ext.gdpr', ~~gdprConsent.gdprApplies); } } if (uspConsent) { - deepSetValue(req, 'regs.ext.us_privacy', uspConsent); + deepSetValue(regs, 'regs.ext.us_privacy', uspConsent); + } + if (config.getConfig('coppa')) { + deepSetValue(regs, 'regs.coppa', 1); } - if (coppa) { - deepSetValue(req, 'regs.coppa', 1); + if (!isEmpty(regs)) { + return regs; } +} + +/** + * Create top-level request object + * @param bidderRequest {BidderRequest} + * @param imps {Object} Impressions + * @param fpd {Object} First party data + * @returns + */ +function makeBaseRequest(bidderRequest, imps, fpd) { + let {auctionId, timeout} = bidderRequest; + let request = { + 'id': auctionId, + 'imp': imps, + 'at': 1, + 'tmax': parseInt(timeout) + }; + if (!isEmpty(fpd.bcat)) { + request.bcat = fpd.bcat; + } + if (!isEmpty(fpd.badv)) { + request.badv = fpd.badv; + } + return request; +} + +/** + * Initialize sync capabilities + * @param bidderRequest {BidderRequest} + */ +function makeSyncInfo(bidderRequest) { + let {bidderCode} = bidderRequest; let syncMethod = getAllowedSyncMethod(bidderCode); if (syncMethod) { - deepSetValue(req, 'ext.adk_usersync', syncMethod); + let res = {}; + deepSetValue(res, 'ext.adk_usersync', syncMethod); + return res; } +} + +/** + * Builds complete rtb request + * @param imps {Object} Collection of rtb impressions + * @param bidderRequest {BidderRequest} + * @param schain {Object=} Supply chain config + * @return {Object} Complete rtb request + */ +function buildRtbRequest(imps, bidderRequest, schain) { + let fpd = config.getConfig('ortb2') || {}; + + let req = mergeDeep( + makeBaseRequest(bidderRequest, imps, fpd), + makeDevice(fpd), + makeSiteOrApp(bidderRequest, fpd), + makeUser(bidderRequest, fpd), + makeRegulations(bidderRequest), + makeSyncInfo(bidderRequest) + ); if (schain) { deepSetValue(req, 'source.ext.schain', schain); } - let eids = getExtendedUserIds(bidderRequest); - if (eids) { - deepSetValue(req, 'user.ext.eids', eids); - } return req; } @@ -430,18 +518,17 @@ function getLanguage() { /** * Creates site description object */ -function createSite(refInfo) { +function createSite(refInfo, fpd) { let url = parseUrl(refInfo.referer); let site = { 'domain': url.hostname, 'page': `${url.protocol}://${url.hostname}${url.pathname}` }; - if (self === top && document.referrer) { + mergeDeep(site, fpd.site); + if (!inIframe() && document.referrer) { site.ref = document.referrer; - } - let keywords = document.getElementsByTagName('meta')['keywords']; - if (keywords && keywords.content) { - site.keywords = keywords.content; + } else { + delete site.ref; } return site; } diff --git a/modules/adlivetechBidAdapter.md b/modules/adlivetechBidAdapter.md new file mode 100644 index 00000000000..612e669ea1a --- /dev/null +++ b/modules/adlivetechBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +Module Name: Adlivetech 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: "adlivetech", + params: { + uid: '1', + bidFloor: 0.5 + } + } + ] + },{ + code: 'test-div', + sizes: [[728, 90]], + bids: [ + { + bidder: "adlivetech", + 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: "adlivetech", + params: { + uid: 11 + } + } + ] + } + ]; +``` diff --git a/modules/adlooxAdServerVideo.js b/modules/adlooxAdServerVideo.js index 7305283039c..bd715cb34f3 100644 --- a/modules/adlooxAdServerVideo.js +++ b/modules/adlooxAdServerVideo.js @@ -9,7 +9,7 @@ import { registerVideoSupport } from '../src/adServerManager.js'; import { command as analyticsCommand, COMMAND } from './adlooxAnalyticsAdapter.js'; import { ajax } from '../src/ajax.js'; -import { EVENTS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; import { targeting } from '../src/targeting.js'; import { logInfo, isFn, logError, isPlainObject, isStr, isBoolean, deepSetValue, deepClone, timestamp, logWarn } from '../src/utils.js'; @@ -74,7 +74,7 @@ function track(options, callback) { bid.ext.adloox.video.adserver = false; analyticsCommand(COMMAND.TRACK, { - eventType: EVENTS.BID_WON, + eventType: CONSTANTS.EVENTS.BID_WON, args: bid }); } diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 6ea1df1b72c..781b8db830a 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -9,7 +9,7 @@ import adapter from '../src/AnalyticsAdapter.js'; import { loadExternalScript } from '../src/adloader.js'; import { auctionManager } from '../src/auctionManager.js'; import { AUCTION_COMPLETED } from '../src/auction.js'; -import { EVENTS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; import find from 'core-js-pure/features/array/find.js'; import { deepAccess, logInfo, isPlainObject, logError, isStr, isNumber, getGptSlotInfoForAdUnitCode, @@ -199,9 +199,9 @@ analyticsAdapter.url = function(url, args, bid) { return url + a2qs(args); } -analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { +analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = function(auctionDetails) { if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; - analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; + analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = NOOP; logMessage(MODULE, 'preloading verification JS'); @@ -214,7 +214,7 @@ analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { insertElement(link); } -analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { +analyticsAdapter[`handle_${CONSTANTS.EVENTS.BID_WON}`] = function(bid) { if (deepAccess(bid, 'ext.adloox.video.adserver')) { logMessage(MODULE, `measuring '${bid.mediaType}' ad unit code '${bid.adUnitCode}' via Ad Server module`); return; diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index e02a1a9df04..666e9aea309 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -1,10 +1,11 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isFn, deepAccess, logMessage } from '../src/utils.js'; +import {config} from '../src/config.js'; const BIDDER_CODE = 'adman'; const AD_URL = 'https://pub.admanmedia.com/?c=o&m=multi'; -const URL_SYNC = 'https://pub.admanmedia.com/?c=o&m=sync'; +const URL_SYNC = 'https://pub.admanmedia.com'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -108,6 +109,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'); } if (traff === VIDEO) { placement.playerSize = bid.mediaTypes[VIDEO].playerSize; @@ -151,19 +153,24 @@ export const spec = { }, getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncUrl = URL_SYNC + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = URL_SYNC + `/${syncType}?pbjs=1`; if (gdprConsent && gdprConsent.consentString) { if (typeof gdprConsent.gdprApplies === 'boolean') { syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; } else { - syncUrl += `&gdpr==0&gdpr_consent=${gdprConsent.consentString}`; + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; } } if (uspConsent && uspConsent.consentString) { syncUrl += `&ccpa_consent=${uspConsent.consentString}`; } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + return [{ - type: 'image', + type: syncType, url: syncUrl }]; } diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index bb91ddcdfc8..dfb76a03804 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,14 +1,15 @@ import { logError } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; const BIDDER_CODE = 'admixer'; -const ALIASES = ['go2net', 'adblender', 'adsyield']; +const ALIASES = ['go2net', 'adblender', 'adsyield', 'futureads']; const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; export const spec = { code: BIDDER_CODE, aliases: ALIASES, - supportedMediaTypes: ['banner', 'video'], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. */ diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index a1dff3d258d..f05cd9f9f32 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -37,7 +37,8 @@ const handleMeta = function () { } const getUsi = function (meta, ortb2, bidderRequest) { - const usi = (meta !== null) ? meta.usi : false; + let usi = (meta !== null && meta.usi) ? meta.usi : false; + if (ortb2 && ortb2.user && ortb2.user.id) { usi = ortb2.user.id } return usi } @@ -55,6 +56,8 @@ export const spec = { const requests = []; const request = []; const ortb2 = config.getConfig('ortb2'); + const bidderConfig = config.getConfig(); + const adnMeta = handleMeta() const usi = getUsi(adnMeta, ortb2, bidderRequest) const segments = getSegmentsFromOrtb(ortb2); @@ -67,7 +70,7 @@ export const spec = { 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'; diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js index 5bbee86df54..e81f6e50054 100644 --- a/modules/adomikAnalyticsAdapter.js +++ b/modules/adomikAnalyticsAdapter.js @@ -12,6 +12,9 @@ const bidRequested = CONSTANTS.EVENTS.BID_REQUESTED; const bidResponse = CONSTANTS.EVENTS.BID_RESPONSE; const bidWon = CONSTANTS.EVENTS.BID_WON; const bidTimeout = CONSTANTS.EVENTS.BID_TIMEOUT; +const ua = navigator.userAgent; + +var _sampled = true; let adomikAdapter = Object.assign(adapter({}), { @@ -47,7 +50,7 @@ let adomikAdapter = Object.assign(adapter({}), type: 'request', event: { bidder: bid.bidder.toUpperCase(), - placementCode: bid.placementCode + placementCode: bid.adUnitCode } }); }); @@ -67,13 +70,20 @@ adomikAdapter.initializeBucketEvents = function() { adomikAdapter.bucketEvents = []; } +adomikAdapter.maxPartLength = function () { + return (ua.includes(' MSIE ')) ? 1600 : 60000; +}; + adomikAdapter.sendTypedEvent = function() { const groupedTypedEvents = adomikAdapter.buildTypedEvents(); const bulkEvents = { + testId: adomikAdapter.currentContext.testId, + testValue: adomikAdapter.currentContext.testValue, uid: adomikAdapter.currentContext.uid, ahbaid: adomikAdapter.currentContext.id, hostname: window.location.hostname, + sampling: adomikAdapter.currentContext.sampling, eventsByPlacementCode: groupedTypedEvents.map(function(typedEventsByType) { let sizes = []; const eventKeys = ['request', 'response', 'winner']; @@ -108,9 +118,10 @@ adomikAdapter.sendTypedEvent = function() { // Encode object in base64 const encodedBuf = window.btoa(stringBulkEvents); - // Create final url and split it in 1600 characters max (+endpoint length) + // Create final url and split it (+endpoint length) const encodedUri = encodeURIComponent(encodedBuf); - const splittedUrl = encodedUri.match(/.{1,1600}/g); + const maxLength = adomikAdapter.maxPartLength(); + const splittedUrl = encodedUri.match(new RegExp(`.{1,${maxLength}}`, 'g')); splittedUrl.forEach((split, i) => { const partUrl = `${split}&id=${adomikAdapter.currentContext.id}&part=${i}&on=${splittedUrl.length - 1}`; @@ -120,8 +131,10 @@ 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) - logInfo('Won event sent to adomik prebid analytic ' + wonEvent); + logInfo('Won event sent to adomik prebid analytic ' + stringWonEvent); // Encode object in base64 const encodedBuf = window.btoa(stringWonEvent); @@ -193,17 +206,28 @@ adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics; adomikAdapter.enableAnalytics = function (config) { adomikAdapter.currentContext = {}; - const initOptions = config.options; - if (initOptions) { - adomikAdapter.currentContext = { - uid: initOptions.id, - url: initOptions.url, - id: '', - timeouted: false, + + _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); } - logInfo('Adomik Analytics enabled with config', initOptions); - adomikAdapter.adapterEnableAnalytics(config); + } else { + logInfo('Adomik Analytics ignored for sampling', config.sampling); } }; diff --git a/modules/adplusBidAdapter.js b/modules/adplusBidAdapter.js new file mode 100644 index 00000000000..c001781a792 --- /dev/null +++ b/modules/adplusBidAdapter.js @@ -0,0 +1,203 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +// #region Constants +export const BIDDER_CODE = 'adplus'; +export const ADPLUS_ENDPOINT = 'https://ssp.ad-plus.com.tr/server/headerBidding'; +export const DGID_CODE = 'adplus_dg_id'; +export const SESSION_CODE = 'adplus_s_id'; +export const storage = getStorageManager(undefined, BIDDER_CODE); +const COOKIE_EXP = 1000 * 60 * 60 * 24; // 1 day +// #endregion + +// #region Helpers +export function isValidUuid (uuid) { + return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( + uuid + ); +} + +function getSessionId() { + let sid = storage.cookiesAreEnabled() && storage.getCookie(SESSION_CODE); + + if ( + !sid || !isValidUuid(sid) + ) { + sid = utils.generateUUID(); + setSessionId(sid); + } + + return sid; +} + +function setSessionId(sid) { + if (storage.cookiesAreEnabled()) { + const expires = new Date(Date.now() + COOKIE_EXP).toISOString(); + + storage.setCookie(SESSION_CODE, sid, expires); + } +} +// #endregion + +// #region Bid request validation +function isBidRequestValid(bid) { + if (!bid) { + utils.logError(BIDDER_CODE, 'bid, can not be empty', bid); + return false; + } + + if (!bid.params) { + utils.logError(BIDDER_CODE, 'bid.params is required.'); + return false; + } + + if (!bid.params.adUnitId || typeof bid.params.adUnitId !== 'string') { + utils.logError( + BIDDER_CODE, + 'bid.params.adUnitId is missing or has wrong type.' + ); + return false; + } + + if (!bid.params.inventoryId || typeof bid.params.inventoryId !== 'string') { + utils.logError( + BIDDER_CODE, + 'bid.params.inventoryId is missing or has wrong type.' + ); + return false; + } + + if ( + !bid.mediaTypes || + !bid.mediaTypes[BANNER] || + !utils.isArray(bid.mediaTypes[BANNER].sizes) || + bid.mediaTypes[BANNER].sizes.length <= 0 || + !utils.isArrayOfNums(bid.mediaTypes[BANNER].sizes[0]) + ) { + utils.logError(BIDDER_CODE, 'Wrong or missing size parameters.'); + return false; + } + + return true; +} +// #endregion + +// #region Building the bid requests +/** + * + * @param {object} bid + * @returns + */ +function createBidRequest(bid) { + // Developer Params + const { + inventoryId, + adUnitId, + extraData, + yearOfBirth, + gender, + categories, + latitude, + longitude, + sdkVersion, + } = bid.params; + + return { + method: 'GET', + url: ADPLUS_ENDPOINT, + data: utils.cleanObj({ + bidId: bid.bidId, + inventoryId, + adUnitId, + adUnitWidth: bid.mediaTypes[BANNER].sizes[0][0], + adUnitHeight: bid.mediaTypes[BANNER].sizes[0][1], + extraData, + yearOfBirth, + gender, + categories, + latitude, + longitude, + sdkVersion: sdkVersion || '1', + session: getSessionId(), + interstitial: 0, + token: typeof window.top === 'object' && window.top[DGID_CODE] ? window.top[DGID_CODE] : undefined, + secure: window.location.protocol === 'https:' ? 1 : 0, + screenWidth: screen.width, + screenHeight: screen.height, + language: window.navigator.language || 'en-US', + pageUrl: window.location.href, + domain: window.location.hostname, + referrer: window.location.referrer, + }), + }; +} + +function buildRequests(validBidRequests, bidderRequest) { + return validBidRequests.map((req) => createBidRequest(req)); +} +// #endregion + +// #region Interpreting Responses +/** + * + * @param {HeaderBiddingResponse} responseData + * @param { object } bidParams + * @returns + */ +function createAdResponse(responseData, bidParams) { + return { + requestId: responseData.requestID, + cpm: responseData.cpm, + currency: responseData.currency, + width: responseData.width, + height: responseData.height, + creativeId: responseData.creativeID, + dealId: responseData.dealID, + netRevenue: responseData.netRevenue, + ttl: responseData.ttl, + ad: responseData.ad, + mediaType: responseData.mediaType, + meta: { + advertiserDomains: responseData.advertiserDomains, + primaryCatId: utils.isArray(responseData.categoryIDs) && responseData.categoryIDs.length > 0 + ? responseData.categoryIDs[0] : undefined, + secondaryCatIds: responseData.categoryIDs, + }, + }; +} + +function interpretResponse(response, request) { + // In case of empty response + if ( + response.body == null || + !utils.isArray(response.body) || + response.body.length === 0 + ) { + return []; + } + const bids = response.body.map((bid) => createAdResponse(bid)); + return bids; +} +// #endregion + +// #region Bidder +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onTimeout(timeoutData) { + utils.logError('Adplus adapter timed out for the auction.', timeoutData); + }, + onBidWon(bid) { + utils.logInfo( + `Adplus adapter won the auction. Bid id: ${bid.bidId}, Ad Unit Id: ${bid.adUnitId}, Inventory Id: ${bid.inventoryId}` + ); + }, +}; + +registerBidder(spec); +// #endregion diff --git a/modules/adplusBidAdapter.md b/modules/adplusBidAdapter.md new file mode 100644 index 00000000000..dce9e4a312f --- /dev/null +++ b/modules/adplusBidAdapter.md @@ -0,0 +1,39 @@ +# Overview + +Module Name: AdPlus Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: adplus.destek@yaani.com.tr + +# Description + +AdPlus Prebid.js Bidder Adapter. Only banner formats are supported. + +About us : https://ssp.ad-plus.com.tr/ + +# Test Parameters + +```javascript +var adUnits = [ + { + code: "div-adplus", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + ], + }, + }, + bids: [ + { + bidder: "adplus", + params: { + inventoryId: "-1", + adUnitId: "-3", + }, + }, + ], + }, +]; +``` diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js new file mode 100644 index 00000000000..5357c1a1ffd --- /dev/null +++ b/modules/adqueryIdSystem.js @@ -0,0 +1,103 @@ +/** + * This module adds Adquery QID to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/adqueryIdSystem + * @requires module:modules/userId + */ + +import {ajax} from '../src/ajax.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {submodule} from '../src/hook.js'; +import * as utils from '../src/utils.js'; + +const MODULE_NAME = 'qid'; +const AU_GVLID = 902; + +export const storage = getStorageManager(AU_GVLID, 'qid'); + +/** + * Param or default. + * @param {String} param + * @param {String} defaultVal + */ +function paramOrDefault(param, defaultVal, arg) { + if (utils.isFn(param)) { + return param(arg); + } else if (utils.isStr(param)) { + return param; + } + return defaultVal; +} + +/** @type {Submodule} */ +export const adqueryIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * IAB TCF Vendor ID + * @type {string} + */ + gvlid: AU_GVLID, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param {{value:string}} value + * @returns {{qid:Object}} + */ + decode(value) { + let qid = storage.getDataFromLocalStorage('qid'); + if (utils.isStr(qid)) { + return {qid: qid}; + } + return (value && typeof value['qid'] === 'string') ? { 'qid': value['qid'] } : 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 (!utils.isPlainObject(config.params)) { + config.params = {}; + } + const url = paramOrDefault(config.params.url, + `https://bidder.adquery.io/prebid/qid`, + config.params.urlArg); + + const resp = function (callback) { + let qid = storage.getDataFromLocalStorage('qid'); + if (utils.isStr(qid)) { + const responseObj = {qid: qid}; + callback(responseObj); + } else { + const callbacks = { + success: response => { + let responseObj; + if (response) { + try { + responseObj = JSON.parse(response); + } catch (error) { + utils.logError(error); + } + } + callback(responseObj); + }, + error: error => { + utils.logError(`${MODULE_NAME}: ID fetch encountered an error`, error); + callback(); + } + }; + ajax(url, callbacks, undefined, {method: 'GET'}); + } + }; + return {callback: resp}; + } +}; + +submodule('userId', adqueryIdSubmodule); diff --git a/modules/adqueryIdSystem.md b/modules/adqueryIdSystem.md new file mode 100644 index 00000000000..3a49ffbe4da --- /dev/null +++ b/modules/adqueryIdSystem.md @@ -0,0 +1,35 @@ +# Adquery QID + +Adquery QID Module. For assistance setting up your module please contact us at [prebid@adquery.io](prebid@adquery.io). + +### Prebid Params + +Individual params may be set for the Adquery ID Submodule. At least one identifier must be set in the params. + +``` +pbjs.setConfig({ + usersync: { + userIds: [{ + name: 'qid', + storage: { + name: 'qid', + type: 'html5' + } + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the Adquery User ID Module integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the Adquery ID module - `"qid"` | `"qid"` | +| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. | | +| 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 html5 local storage where the user ID will be stored. | `"qid"` | +| 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 Adquery 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 | `{"qid": "2abf9f001fcd81241b67"}` | +| params | Optional | Object | Used to store params for the id system | +| params.url | Optional | String | Set an alternate GET url for qid with this parameter | +| params.urlArg | Optional | Object | Optional url parameter for params.url | diff --git a/modules/adxcgBidAdapter.md b/modules/adxcgBidAdapter.md index 8eccdb11dee..1e4ef9cd6f9 100644 --- a/modules/adxcgBidAdapter.md +++ b/modules/adxcgBidAdapter.md @@ -34,31 +34,34 @@ Module that connects to an Adxcg.com zone to fetch bids. code: 'native-ad-div', mediaTypes: { native: { - image: { + image: { sendId: false, - required: true, - sizes: [80, 80] + required: false, + sizes: [127, 83] }, icon: { - sendId: true, - }, - brand: { + sizes: [80, 80], + required: false, sendId: true, }, title: { sendId: false, - required: true, + required: false, len: 75 }, body: { sendId: false, - required: true, + required: false, len: 200 }, - sponsoredBy: { + cta: { sendId: false, required: false, - len: 20 + len: 75 + }, + sponsoredBy: { + sendId: false, + required: false } } }, @@ -73,21 +76,19 @@ Module that connects to an Adxcg.com zone to fetch bids. code: 'video-div', mediaTypes: { video: { - playerSize: [640, 480], - context: 'instream', - mimes: ['video/mp4'], - protocols: [5, 6, 8], - playback_method: ['auto_play_sound_off'] - } + playerSize: [640, 480], + context: 'instream', + mimes: ['video/mp4'], + protocols: [2, 3, 5, 6, 8], + playback_method: ['auto_play_sound_off'], + maxduration: 100, + skip: 1 + } }, bids: [{ bidder: 'adxcg', params: { - adzoneid: '20', - video: { - maxduration: 100, - skippable: true - } + adzoneid: '20' } }] } diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 334309aec5c..155e8ca3c7a 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -1,6 +1,7 @@ import { deepAccess, buildUrl, 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 'core-js-pure/features/array/find.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; @@ -97,17 +98,21 @@ export const spec = { PageRefreshed: getPageRefreshed() }; - if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent) { payload.gdprConsent = { consentString: bidderRequest.gdprConsent.consentString, consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : null }; } - if (bidderRequest && bidderRequest.uspConsent) { + if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } + if (deepAccess(bidderRequest, 'userId')) { + payload.userId = createEidsArray(bidderRequest.userId); + } + const data = JSON.stringify(payload); const options = { withCredentials: true @@ -175,11 +180,13 @@ function getCanonicalUrl() { /* Get mediatype from bidRequest */ function getMediatype(bidRequest) { + if (deepAccess(bidRequest, 'mediaTypes.banner')) { + return BANNER; + } if (deepAccess(bidRequest, 'mediaTypes.video')) { return VIDEO; - } else if (deepAccess(bidRequest, 'mediaTypes.banner')) { - return BANNER; - } else if (deepAccess(bidRequest, 'mediaTypes.native')) { + } + if (deepAccess(bidRequest, 'mediaTypes.native')) { return NATIVE; } } @@ -340,7 +347,7 @@ function getTrackers(eventsArray, jsTrackers) { function getVideoAd(response) { var adJson = {}; - if (typeof response.Ad === 'string') { + 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'); } @@ -473,13 +480,15 @@ function createBid(response, bidRequests) { meta: response.Meta || { advertiserDomains: [] } }; - if (request && request.Native) { + // retreive video response if present + const vast64 = response.Vast || getVideoAd(response); + if (vast64) { + bid.vastXml = window.atob(vast64); + bid.mediaType = 'video'; + } else if (request.Native) { + // format Native response if Native was requested bid.native = getNativeAssets(response, request.Native); bid.mediaType = 'native'; - } else if (request && request.Video) { - const vast64 = response.Vast || getVideoAd(response); - bid.vastXml = vast64 ? window.atob(vast64) : ''; - bid.mediaType = 'video'; } else { bid.width = response.Width; bid.height = response.Height; diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js index 96bdf153e3f..53249e92a77 100644 --- a/modules/aniviewBidAdapter.js +++ b/modules/aniviewBidAdapter.js @@ -309,7 +309,7 @@ function getUserSyncs(syncOptions, serverResponses) { export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo'], + aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo', 'didnavideo'], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid, buildRequests, diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 7fb2d372300..5480d1eedca 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -1,4 +1,4 @@ -import { convertCamelToUnderscore, isArray, isNumber, isPlainObject, logError, logInfo, deepAccess, logMessage, convertTypes, isStr, getParameterByName, deepClone, chunk, logWarn, getBidRequest, createTrackPixelHtml, isEmpty, transformBidderParamKeywords, getMaxValueFromArray, fill, getMinValueFromArray, isArrayOfNums, isFn, getGptSlotInfoForAdUnitCode } from '../src/utils.js'; +import { convertCamelToUnderscore, isArray, isNumber, isPlainObject, logError, logInfo, deepAccess, logMessage, convertTypes, isStr, getParameterByName, deepClone, chunk, logWarn, getBidRequest, createTrackPixelHtml, isEmpty, transformBidderParamKeywords, getMaxValueFromArray, fill, getMinValueFromArray, isArrayOfNums, isFn, isAllowZeroCpmBidsEnabled } from '../src/utils.js'; import { Renderer } from '../src/Renderer.js'; import { config } from '../src/config.js'; import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory.js'; @@ -78,7 +78,6 @@ export const spec = { { code: 'districtm', gvlid: 144 }, { code: 'adasta' }, { code: 'beintoo', gvlid: 618 }, - { code: 'targetVideo' }, ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -293,7 +292,8 @@ export const spec = { serverResponse.tags.forEach(serverBid => { const rtbBid = getRtbBid(serverBid); if (rtbBid) { - if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const cpmCheck = (isAllowZeroCpmBidsEnabled(bidderRequest.bidderCode)) ? rtbBid.cpm >= 0 : rtbBid.cpm > 0; + if (cpmCheck && includes(this.supportedMediaTypes, rtbBid.ad_type)) { const bid = newBid(serverBid, rtbBid, bidderRequest); bid.mediaType = parseMediaType(rtbBid); bids.push(bid); @@ -598,6 +598,22 @@ function newBid(serverBid, rtbBid, bidderRequest) { bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); } + // temporary function; may remove at later date if/when adserver fully supports dchain + function setupDChain(rtbBid) { + let dchain = { + ver: '1.0', + complete: 0, + nodes: [{ + bsid: rtbBid.buyer_member_id.toString() + }], + }; + + return dchain; + } + if (rtbBid.buyer_member_id) { + bid.meta = Object.assign({}, bid.meta, {dchain: setupDChain(rtbBid)}); + } + if (rtbBid.brand_id) { bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id }); } @@ -1072,15 +1088,14 @@ function hideSASIframe(elementId) { } function outstreamRender(bid) { - const code = getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId || bid.adUnitCode; - hidedfpContainer(code); - hideSASIframe(code); + hidedfpContainer(bid.adUnitCode); + hideSASIframe(bid.adUnitCode); // push to render queue because ANOutstreamVideo may not be loaded yet bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ tagId: bid.adResponse.tag_id, sizes: [bid.getSize().split('x')], - targetId: code, // target div id to render video + targetId: bid.adUnitCode, // target div id to render video uuid: bid.adResponse.uuid, adResponse: bid.adResponse, rendererOptions: bid.renderer.getConfig() diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index a882a796851..e705156d4a2 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -1,4 +1,4 @@ -import { logWarn, deepAccess, isArray, parseSizesInput, isFn, parseUrl, getUniqueIdentifierStr } from '../src/utils.js'; +import { logWarn, deepAccess, deepSetValue, deepClone, isArray, parseSizesInput, isFn, parseUrl, getUniqueIdentifierStr } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; @@ -6,7 +6,7 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; -const ADAPTER_VERSION = '1.18'; +const ADAPTER_VERSION = '1.19'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; const CURRENCY = 'USD'; @@ -360,6 +360,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 payload = { isPrebid: true, appId: appId, @@ -378,6 +379,7 @@ function createVideoRequestData(bid, bidderRequest) { displaymanagerver: ADAPTER_VERSION }], site: { + ...deepAccess(ortb2, 'site', {}), page: topLocation.href, domain: topLocation.hostname }, @@ -389,39 +391,32 @@ function createVideoRequestData(bid, bidderRequest) { js: 1, geo: {} }, - regs: { - ext: {} - }, - source: { - ext: {} - }, - user: { - ext: {} - }, + app: deepAccess(ortb2, 'app'), + user: deepAccess(ortb2, 'user'), cur: [CURRENCY] }; if (bidderRequest && bidderRequest.uspConsent) { - payload.regs.ext.us_privacy = bidderRequest.uspConsent; + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } if (bidderRequest && bidderRequest.gdprConsent) { let { gdprApplies, consentString } = bidderRequest.gdprConsent; - payload.regs.ext.gdpr = gdprApplies ? 1 : 0; - payload.user.ext.consent = consentString; + deepSetValue(payload, 'regs.ext.gdpr', gdprApplies ? 1 : 0); + deepSetValue(payload, 'user.ext.consent', consentString); } if (bid.schain) { - payload.source.ext.schain = bid.schain; + deepSetValue(payload, 'source.ext.schain', bid.schain); } if (eids.length > 0) { - payload.user.ext.eids = eids; + deepSetValue(payload, 'user.ext.eids', eids); } let connection = navigator.connection || navigator.webkitConnection; if (connection && connection.effectiveType) { - payload.device.connectiontype = connection.effectiveType; + deepSetValue(payload, 'device.connectiontype', connection.effectiveType); } return payload; @@ -439,8 +434,10 @@ function createBannerRequestData(bids, bidderRequest) { sizes: getBannerSizes(bid) }; }); + let ortb2 = deepClone(config.getConfig('ortb2')); let payload = { slots: slots, + ortb2: ortb2, page: topLocation.href, domain: topLocation.hostname, search: topLocation.search, diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index a6bc8a5687d..2e74170fcaf 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -36,7 +36,7 @@ export const spec = { */ buildRequests: function(validBidRequests, bidderRequest) { const slots = validBidRequests.map(beOpRequestSlotsMaker); - let pageUrl = deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || deepAccess(window, 'location.href'); + 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]; @@ -99,16 +99,18 @@ export const spec = { } function buildTrackingParams(data, info, value) { + const accountId = data.params.accountId; return { - pid: data.params.accountId, + pid: accountId === undefined ? data.ad.match(/account: \“([a-f\d]{24})\“/)[1] : accountId, nid: data.params.networkId, nptnid: data.params.networkPartnerId, - bid: data.bidId, + bid: data.bidId || data.requestId, sl_n: data.adUnitCode, aid: data.auctionId, se_ca: 'bid', se_ac: info, - se_va: value + se_va: value, + url: window.location.href }; } diff --git a/modules/bidViewability.js b/modules/bidViewability.js index c3b72cda8d4..545d57940da 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -4,7 +4,7 @@ import { config } from '../src/config.js'; import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; import { logWarn, isFn, triggerPixel } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import adapterManager, { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; @@ -70,12 +70,12 @@ export let impressionViewableHandler = (globalModuleConfig, slot, event) => { // trigger respective bidder's onBidViewable handler adapterManager.callBidViewableBidder(respectiveBid.bidder, respectiveBid); // emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels - events.emit(EVENTS.BID_VIEWABLE, respectiveBid); + events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, respectiveBid); } }; export let init = () => { - events.on(EVENTS.AUCTION_INIT, () => { + events.on(CONSTANTS.EVENTS.AUCTION_INIT, () => { // read the config for the module const globalModuleConfig = config.getConfig(MODULE_NAME) || {}; // do nothing if module-config.enabled is not set to true diff --git a/modules/bidViewabilityIO.js b/modules/bidViewabilityIO.js index d936fb4aeec..ff7ec70e32c 100644 --- a/modules/bidViewabilityIO.js +++ b/modules/bidViewabilityIO.js @@ -1,7 +1,7 @@ import { logMessage } from '../src/utils.js'; import { config } from '../src/config.js'; import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; const MODULE_NAME = 'bidViewabilityIO'; const CONFIG_ENABLED = 'enabled'; @@ -42,7 +42,7 @@ export let getViewableOptions = (bid) => { export let markViewed = (bid, entry, observer) => { return () => { observer.unobserve(entry.target); - events.emit(EVENTS.BID_VIEWABLE, bid); + events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, bid); _logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} was viewed`); } } @@ -77,7 +77,7 @@ export let init = () => { if (conf[MODULE_NAME][CONFIG_ENABLED] && CLIENT_SUPPORTS_IO) { // if the module is enabled and the browser supports Intersection Observer, // then listen to AD_RENDER_SUCCEEDED to setup IO's for supported mediaTypes - events.on(EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => { + events.on(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => { if (isSupportedMediaType(bid)) { let viewable = new IntersectionObserver(viewCallbackFactory(bid), getViewableOptions(bid)); let element = document.getElementById(bid.adUnitCode); diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 70349f95cde..45b6c46c2df 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -1,7 +1,6 @@ // 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' 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' @@ -174,6 +173,8 @@ export const buildRequests = (_, bidderRequest) => { pageUrl: bidderRequest.refererInfo.referer, pageDescription: getMetaValue(META_DESCRIPTION), keywords: getKeywords().join(','), + gdpr: false, + gdpr_consent: '', pageTitle: document.title, } diff --git a/modules/codefuelBidAdapter.js b/modules/codefuelBidAdapter.js index ecb56c00d29..b9da86ac24e 100644 --- a/modules/codefuelBidAdapter.js +++ b/modules/codefuelBidAdapter.js @@ -1,5 +1,4 @@ import { deepAccess, isArray } from '../src/utils.js'; -import { config } from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'codefuel'; @@ -34,8 +33,7 @@ export const spec = { const devicetype = getDeviceType() const publisher = setOnAny(validBidRequests, 'params.publisher'); const cur = CURRENCY; - // const endpointUrl = 'http://localhost:5000/prebid' - const endpointUrl = config.getConfig('codefuel.bidderUrl'); + const endpointUrl = 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid' const timeout = bidderRequest.timeout; validBidRequests.forEach(bid => bid.netRevenue = 'net'); diff --git a/modules/codefuelBidAdapter.md b/modules/codefuelBidAdapter.md index 321ae3b6644..4e1a0dd6409 100644 --- a/modules/codefuelBidAdapter.md +++ b/modules/codefuelBidAdapter.md @@ -21,7 +21,7 @@ You will receive the URLs when contacting us. ``` pbjs.setConfig({ codefuel: { - bidderUrl: 'https://ai-i-codefuel-ds-rtb-us-east-1-k8s-internal.seccint.com/prebid', + bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid', usersyncUrl: 'https://usersync-url.com' } }); @@ -74,7 +74,7 @@ pbjs.setConfig({ pbjs.setConfig({ codefuel: { - bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid' } }); ``` @@ -105,7 +105,7 @@ pbjs.setConfig({ pbjs.setConfig({ codefuel: { - bidderUrl: 'https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/' + bidderUrl: 'https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid' } }); ``` diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 5e7c58f28ad..72df7c7b465 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -1,6 +1,7 @@ import { getWindowTop, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'colossusssp'; const G_URL = 'https://colossusssp.com/?c=o&m=multi'; @@ -46,7 +47,10 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placement_id)); + const validPlacamentId = bid.params && !isNaN(bid.params.placement_id); + const validGroupId = bid.params && !isNaN(bid.params.group_id); + + return Boolean(bid.bidId && (validPlacamentId || validGroupId)); }, /** @@ -60,13 +64,13 @@ export const spec = { const location = winTop.location; let placements = []; let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language : '', - 'secure': location.protocol === 'https:' ? 1 : 0, - 'host': location.host, - 'page': location.pathname, - 'placements': placements, + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language : '', + secure: location.protocol === 'https:' ? 1 : 0, + host: location.host, + page: location.pathname, + placements: placements, }; if (bidderRequest) { @@ -84,6 +88,7 @@ export const spec = { 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, @@ -175,6 +180,12 @@ export const spec = { type: 'image', url: G_URL_SYNC }]; + }, + + onBidWon: (bid) => { + if (bid.nurl) { + ajax(bid.nurl, null); + } } }; diff --git a/modules/colossussspBidAdapter.md b/modules/colossussspBidAdapter.md index 8797c648c95..4187dfbf36e 100644 --- a/modules/colossussspBidAdapter.md +++ b/modules/colossussspBidAdapter.md @@ -26,5 +26,19 @@ Module that connects to Colossus SSP demand sources traffic: 'banner' } }] - ]; + }, { + code: 'placementid_1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'colossusssp', + params: { + group_id: 0, + traffic: 'banner' + } + }] + }]; ``` diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js new file mode 100644 index 00000000000..77f918276bc --- /dev/null +++ b/modules/compassBidAdapter.js @@ -0,0 +1,208 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'compass'; +const AD_URL = 'https://sa-lb.deliverimp.com/pbjs'; +const SYNC_URL = 'https://sa-cs.deliverimp.com'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/compassBidAdapter.md b/modules/compassBidAdapter.md new file mode 100644 index 00000000000..18d52c12384 --- /dev/null +++ b/modules/compassBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Compass Bidder Adapter +Module Type: Compass Bidder Adapter +Maintainer: sa-support@brightcom.com +``` + +# Description + +Connects to Compass exchange for bids. +Compass bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'compass', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'compass', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'compass', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 65ffc9a4def..ecd0c0eec4b 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -371,7 +371,13 @@ function processCmpData(consentObject, hookConfig) { * General timeout callback when interacting with CMP takes too long. */ function cmpTimedOut(hookConfig) { - cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig); + if (cmpVersion === 2) { + logWarn(`No response from CMP, continuing auction...`) + storeConsentData(undefined); + exitModule(null, hookConfig) + } else { + cmpFailed('CMP workflow exceeded timeout threshold.', hookConfig); + } } /** diff --git a/modules/consumableBidAdapter.md b/modules/consumableBidAdapter.md index 2189494ebd4..ba472899c49 100644 --- a/modules/consumableBidAdapter.md +++ b/modules/consumableBidAdapter.md @@ -4,7 +4,7 @@ Module Name: Consumable Bid Adapter Module Type: Consumable Adapter -Maintainer: naffis@consumable.com +Maintainer: prebid@consumable.com # Description diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index b98b72b59ad..812ec53d686 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -5,6 +5,7 @@ import { auctionManager } from '../src/auctionManager.js'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; import { getStorageManager } from '../src/storageManager.js'; +import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'craft'; const URL_BASE = 'https://gacraft.jp/prebid-v3'; @@ -110,9 +111,10 @@ export const spec = { }, onBidWon: function(bid) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', bid._prebidWon); - xhr.send(); + ajax(bid._prebidWon, null, null, { + method: 'POST', + contentType: 'application/json' + }); } }; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index a4ec99e4fa8..4dfb5d38f4c 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -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 = 113; +export const FAST_BID_VERSION_CURRENT = 117; 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'; diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index c716a3c9cd6..ecf7b3aaac4 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -33,15 +33,37 @@ function getFromAllStorages(key) { return storage.getCookie(key) || storage.getDataFromLocalStorage(key); } -function saveOnAllStorages(key, value) { +function saveOnAllStorages(key, value, hostname) { if (key && value) { - storage.setCookie(key, value, expirationString); storage.setDataInLocalStorage(key, value); + setCookieOnAllDomains(key, value, expirationString, hostname, true); } } -function deleteFromAllStorages(key) { - storage.setCookie(key, '', pastDateString); +function setCookieOnAllDomains(key, value, expiration, hostname, stopOnSuccess) { + const subDomains = hostname.split('.'); + for (let i = 0; i < subDomains.length; ++i) { + // Try to write the cookie on this subdomain (we want it to be stored only on the TLD+1) + const domain = subDomains.slice(subDomains.length - i - 1, subDomains.length).join('.'); + + try { + storage.setCookie(key, value, expiration, null, '.' + domain); + + if (stopOnSuccess) { + // Try to read the cookie to check if we wrote it + const ck = storage.getCookie(key); + if (ck && ck === value) { + break; + } + } + } catch (error) { + + } + } +} + +function deleteFromAllStorages(key, hostname) { + setCookieOnAllDomains(key, '', pastDateString, hostname, true); storage.removeDataFromLocalStorage(key); } @@ -89,15 +111,15 @@ function callCriteoUserSync(parsedCriteoData, gdprString, callback) { const urlsToCall = typeof jsonResponse.acwsUrl === 'string' ? [jsonResponse.acwsUrl] : jsonResponse.acwsUrl; urlsToCall.forEach(url => triggerPixel(url)); } else if (jsonResponse.bundle) { - saveOnAllStorages(bundleStorageKey, jsonResponse.bundle); + saveOnAllStorages(bundleStorageKey, jsonResponse.bundle, domain); } if (jsonResponse.bidId) { - saveOnAllStorages(bididStorageKey, jsonResponse.bidId); + saveOnAllStorages(bididStorageKey, jsonResponse.bidId, domain); const criteoId = { criteoId: jsonResponse.bidId }; callback(criteoId); } else { - deleteFromAllStorages(bididStorageKey); + deleteFromAllStorages(bididStorageKey, domain); callback(); } }, diff --git a/modules/currency.js b/modules/currency.js index dc77ee21430..5f7add764ad 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -1,7 +1,7 @@ import { logInfo, logWarn, logError, logMessage } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { createBid } from '../src/bidfactory.js'; -import { STATUS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; @@ -219,7 +219,7 @@ function wrapFunction(fn, context, params) { } } catch (e) { logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e); - params[1] = createBid(STATUS.NO_BID, { + params[1] = createBid(CONSTANTS.STATUS.NO_BID, { bidder: bid.bidderCode || bid.bidder, bidId: bid.requestId }); diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index 9e082dffbf4..c9caa78e5e7 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -26,6 +26,7 @@ export const RENDERER_URL = 'https://cdn.cwi.re/prebid/renderer/LATEST/renderer. export const CW_PAGE_VIEW_ID = generateUUID(); const LS_CWID_KEY = 'cw_cwid'; const CW_GROUPS_QUERY = 'cwgroups'; +const CW_CREATIVE_QUERY = 'cwcreative'; const storage = getStorageManager(); @@ -161,6 +162,7 @@ export const spec = { let refgroups = []; + const cwCreativeId = getQueryVariable(CW_CREATIVE_QUERY); const rgQuery = getQueryVariable(CW_GROUPS_QUERY); if (rgQuery !== null) { refgroups = rgQuery.split(','); @@ -171,6 +173,7 @@ export const spec = { const payload = { cwid: localStorageCWID, refgroups, + cwcreative: cwCreativeId, slots: slots, httpRef: referer || '', pageViewId: CW_PAGE_VIEW_ID, diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js new file mode 100644 index 00000000000..cdcc9f1d038 --- /dev/null +++ b/modules/dailyhuntBidAdapter.js @@ -0,0 +1,435 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as mediaTypes from '../src/mediaTypes.js'; +import {deepAccess, _map, isEmpty} from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import find from 'core-js-pure/features/array/find.js'; +import { OUTSTREAM, INSTREAM } from '../src/video.js'; + +const BIDDER_CODE = 'dailyhunt'; +const BIDDER_ALIAS = 'dh'; +const SUPPORTED_MEDIA_TYPES = [mediaTypes.BANNER, mediaTypes.NATIVE, mediaTypes.VIDEO]; + +const PROD_PREBID_ENDPOINT_URL = 'https://pbs.dailyhunt.in/openrtb2/auction?partner='; +const PROD_PREBID_TEST_ENDPOINT_URL = 'https://qa-pbs-van.dailyhunt.in/openrtb2/auction?partner='; + +const ORTB_NATIVE_TYPE_MAPPING = { + img: { + '3': 'image', + '1': 'icon' + }, + data: { + '1': 'sponsoredBy', + '2': 'body', + '3': 'rating', + '4': 'likes', + '5': 'downloads', + '6': 'price', + '7': 'salePrice', + '8': 'phone', + '9': 'address', + '10': 'body2', + '11': 'displayUrl', + '12': 'cta' + } +} + +const ORTB_NATIVE_PARAMS = { + title: { + id: 0, + name: 'title' + }, + icon: { + id: 1, + type: 1, + name: 'img' + }, + image: { + id: 2, + type: 3, + name: 'img' + }, + sponsoredBy: { + id: 3, + name: 'data', + type: 1 + }, + body: { + id: 4, + name: 'data', + type: 2 + }, + cta: { + id: 5, + type: 12, + name: 'data' + }, + body2: { + id: 4, + name: 'data', + type: 10 + }, +}; + +// Encode URI. +const _encodeURIComponent = function (a) { + let b = window.encodeURIComponent(a); + b = b.replace(/'/g, '%27'); + return b; +} + +// Extract key from collections. +const extractKeyInfo = (collection, key) => { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i].params, key); + if (result) { + return result; + } + } + return undefined +} + +// Flattern Array. +const flatten = (arr) => { + return [].concat(...arr); +} + +const createOrtbRequest = (validBidRequests, bidderRequest) => { + let device = createOrtbDeviceObj(validBidRequests); + let user = createOrtbUserObj(validBidRequests) + let site = createOrtbSiteObj(validBidRequests, bidderRequest.refererInfo.referer) + return { + id: bidderRequest.auctionId, + imp: [], + site, + device, + user, + }; +} + +const createOrtbDeviceObj = (validBidRequests) => { + let device = { ...extractKeyInfo(validBidRequests, `device`) }; + device.ua = navigator.userAgent; + return device; +} + +const createOrtbUserObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `user`) }) + +const createOrtbSiteObj = (validBidRequests, page) => { + let site = { ...extractKeyInfo(validBidRequests, `site`), page }; + let publisher = createOrtbPublisherObj(validBidRequests); + if (!site.publisher) { + site.publisher = publisher + } + return site +} + +const createOrtbPublisherObj = (validBidRequests) => ({ ...extractKeyInfo(validBidRequests, `publisher`) }) + +// get bidFloor Function for different creatives +function getBidFloor(bid, creative) { + let floorInfo = typeof (bid.getFloor) == 'function' ? bid.getFloor({ currency: 'USD', mediaType: creative, size: '*' }) : {}; + return Math.floor(floorInfo.floor || (bid.params.bidfloor ? bid.params.bidfloor : 0.0)); +} + +const createOrtbImpObj = (bid) => { + let params = bid.params + let testMode = !!bid.params.test_mode + + // Validate Banner Request. + let bannerObj = deepAccess(bid.mediaTypes, `banner`); + let nativeObj = deepAccess(bid.mediaTypes, `native`); + let videoObj = deepAccess(bid.mediaTypes, `video`); + + let imp = { + id: bid.bidId, + ext: { + dailyhunt: { + placement_id: params.placement_id, + publisher_id: params.publisher_id, + partner: params.partner_name + } + } + }; + + // Test Mode Campaign. + if (testMode) { + imp.ext.test_mode = testMode; + } + + if (bannerObj) { + imp.banner = { + ...createOrtbImpBannerObj(bid, bannerObj) + } + imp.bidfloor = getBidFloor(bid, 'banner'); + } else if (nativeObj) { + imp.native = { + ...createOrtbImpNativeObj(bid, nativeObj) + } + imp.bidfloor = getBidFloor(bid, 'native'); + } else if (videoObj) { + imp.video = { + ...createOrtbImpVideoObj(bid, videoObj) + } + imp.bidfloor = getBidFloor(bid, 'video'); + } + return imp; +} + +const createOrtbImpBannerObj = (bid, bannerObj) => { + let format = []; + bannerObj.sizes.forEach(size => format.push({ w: size[0], h: size[1] })) + + return { + id: 'banner-' + bid.bidId, + format + } +} + +const createOrtbImpNativeObj = (bid, nativeObj) => { + const assets = _map(bid.nativeParams, (bidParams, key) => { + const props = ORTB_NATIVE_PARAMS[key]; + const asset = { + required: bidParams.required & 1, + }; + if (props) { + let h = 0; + let w = 0; + + asset.id = props.id; + + if (bidParams.sizes) { + const sizes = flatten(bidParams.sizes); + w = sizes[0]; + h = sizes[1]; + } + + asset[props.name] = { + len: bidParams.len ? bidParams.len : 20, + type: props.type, + w, + h + }; + + return asset; + } + }).filter(Boolean); + let request = { + assets, + ver: '1,0' + } + return { request: JSON.stringify(request) }; +} + +const createOrtbImpVideoObj = (bid, videoObj) => { + let obj = {}; + let params = bid.params + if (!isEmpty(bid.params.video)) { + obj = { + topframe: 1, + skip: params.video.skippable || 0, + linearity: params.video.linearity || 1, + minduration: params.video.minduration || 5, + maxduration: params.video.maxduration || 60, + mimes: params.video.mimes || ['video/mp4'], + protocols: getProtocols(params.video), + w: params.video.playerSize[0][0], + h: params.video.playerSize[0][1], + }; + } else { + obj = { + mimes: ['video/mp4'], + }; + } + obj.ext = { + ...videoObj, + } + return obj; +} + +export function getProtocols({protocols}) { + let defaultValue = [2, 3, 5, 6, 7, 8]; + let listProtocols = [ + {key: 'VAST_1_0', value: 1}, + {key: 'VAST_2_0', value: 2}, + {key: 'VAST_3_0', value: 3}, + {key: 'VAST_1_0_WRAPPER', value: 4}, + {key: 'VAST_2_0_WRAPPER', value: 5}, + {key: 'VAST_3_0_WRAPPER', value: 6}, + {key: 'VAST_4_0', value: 7}, + {key: 'VAST_4_0_WRAPPER', value: 8} + ]; + if (protocols) { + return listProtocols.filter(p => { + return protocols.indexOf(p.key) !== -1 + }).map(p => p.value); + } else { + return defaultValue; + } +} + +const createServerRequest = (ortbRequest, validBidRequests, isTestMode = 'false') => ({ + method: 'POST', + url: isTestMode === 'true' ? PROD_PREBID_TEST_ENDPOINT_URL + validBidRequests[0].params.partner_name : PROD_PREBID_ENDPOINT_URL + validBidRequests[0].params.partner_name, + data: JSON.stringify(ortbRequest), + options: { + contentType: 'application/json', + withCredentials: true + }, + bids: validBidRequests +}) + +const createPrebidBannerBid = (bid, bidResponse) => ({ + requestId: bid.bidId, + cpm: bidResponse.price.toFixed(2), + creativeId: bidResponse.crid, + width: bidResponse.w, + height: bidResponse.h, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: 'USD', + ad: bidResponse.adm, + mediaType: 'banner', + winUrl: bidResponse.nurl, + adomain: bidResponse.adomain +}) + +const createPrebidNativeBid = (bid, bidResponse) => ({ + requestId: bid.bidId, + cpm: bidResponse.price.toFixed(2), + creativeId: bidResponse.crid, + currency: 'USD', + ttl: 360, + netRevenue: bid.netRevenue === 'net', + native: parseNative(bidResponse), + mediaType: 'native', + winUrl: bidResponse.nurl, + width: bidResponse.w, + height: bidResponse.h, + adomain: bidResponse.adomain +}) + +const parseNative = (bid) => { + let adm = JSON.parse(bid.adm) + const { assets, link, imptrackers, jstracker } = adm.native; + const result = { + clickUrl: _encodeURIComponent(link.url), + clickTrackers: link.clicktrackers || [], + impressionTrackers: imptrackers || [], + javascriptTrackers: jstracker ? [ jstracker ] : [] + }; + assets.forEach(asset => { + if (!isEmpty(asset.title)) { + result.title = asset.title.text + } else if (!isEmpty(asset.img)) { + result[ORTB_NATIVE_TYPE_MAPPING.img[asset.img.type]] = { + url: asset.img.url, + height: asset.img.h, + width: asset.img.w + } + } else if (!isEmpty(asset.data)) { + result[ORTB_NATIVE_TYPE_MAPPING.data[asset.data.type]] = asset.data.value + } + }); + + return result; +} + +const createPrebidVideoBid = (bid, bidResponse) => { + let videoBid = { + requestId: bid.bidId, + cpm: bidResponse.price.toFixed(2), + creativeId: bidResponse.crid, + width: bidResponse.w, + height: bidResponse.h, + ttl: 360, + netRevenue: bid.netRevenue === 'net', + currency: 'USD', + mediaType: 'video', + winUrl: bidResponse.nurl, + adomain: bidResponse.adomain + }; + + let videoContext = bid.mediaTypes.video.context; + switch (videoContext) { + case OUTSTREAM: + videoBid.vastXml = bidResponse.adm; + break; + case INSTREAM: + videoBid.videoCacheKey = bidResponse.ext.bidder.cacheKey; + videoBid.vastUrl = bidResponse.ext.bidder.vastUrl; + break; + } + return videoBid; +} + +const getQueryVariable = (variable) => { + let query = window.location.search.substring(1); + let vars = query.split('&'); + for (var i = 0; i < vars.length; i++) { + let pair = vars[i].split('='); + if (decodeURIComponent(pair[0]) == variable) { + return decodeURIComponent(pair[1]); + } + } + return false; +} + +export const spec = { + code: BIDDER_CODE, + + aliases: [BIDDER_ALIAS], + + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + + isBidRequestValid: bid => !!bid.params.placement_id && !!bid.params.publisher_id && !!bid.params.partner_name, + + buildRequests: function (validBidRequests, bidderRequest) { + let serverRequests = []; + + // ORTB Request. + let ortbReq = createOrtbRequest(validBidRequests, bidderRequest); + + validBidRequests.forEach((bid) => { + let imp = createOrtbImpObj(bid) + ortbReq.imp.push(imp); + }); + + serverRequests.push({ ...createServerRequest(ortbReq, validBidRequests, getQueryVariable('dh_test')) }); + + return serverRequests; + }, + + interpretResponse: function (serverResponse, request) { + const { seatbid } = serverResponse.body; + let bids = request.bids; + let prebidResponse = []; + + let seatBids = seatbid[0].bid; + + seatBids.forEach(ortbResponseBid => { + let bidId = ortbResponseBid.impid; + let actualBid = find(bids, (bid) => bid.bidId === bidId); + let bidMediaType = ortbResponseBid.ext.prebid.type + switch (bidMediaType) { + case mediaTypes.BANNER: + prebidResponse.push(createPrebidBannerBid(actualBid, ortbResponseBid)); + break; + case mediaTypes.NATIVE: + prebidResponse.push(createPrebidNativeBid(actualBid, ortbResponseBid)); + break; + case mediaTypes.VIDEO: + prebidResponse.push(createPrebidVideoBid(actualBid, ortbResponseBid)); + break; + } + }) + return prebidResponse; + }, + + onBidWon: function(bid) { + ajax(bid.winUrl, null, null, { + method: 'GET' + }) + } +} + +registerBidder(spec); diff --git a/modules/dailyhuntBidAdapter.md b/modules/dailyhuntBidAdapter.md index acfd20a4de0..a08b66fb826 100644 --- a/modules/dailyhuntBidAdapter.md +++ b/modules/dailyhuntBidAdapter.md @@ -99,3 +99,7 @@ Dailyhunt bid adapter supports Banner, Native and Video. } ]; ``` + +## latest commit has all the required support for latest version of prebid above 6.x +## Dailyhunt adapter was there till 4.x and then removed in version 5.x of prebid. +## this doc has been also submitted to https://github.com/prebid/prebid.github.io \ No newline at end of file diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 197ba19b1d6..43039e070c3 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -94,7 +94,7 @@ export const spec = { code: 'datablocks', // DATABLOCKS SCOPED OBJECT - db_obj: {metrics_host: 'prebid.datablocks.net', metrics: [], metrics_timer: null, metrics_queue_time: 1000, vis_optout: false, source_id: 0}, + db_obj: {metrics_host: 'prebid.dblks.net', metrics: [], metrics_timer: null, metrics_queue_time: 1000, vis_optout: false, source_id: 0}, // STORE THE DATABLOCKS BUYERID IN STORAGE store_dbid: function(dbid) { @@ -388,12 +388,12 @@ export const spec = { }; let sourceId = validRequests[0].params.source_id || 0; - let host = validRequests[0].params.host || 'prebid.datablocks.net'; + let host = validRequests[0].params.host || 'prebid.dblks.net'; // RETURN WITH THE REQUEST AND PAYLOAD return { method: 'POST', - url: `https://${sourceId}.${host}/openrtb/?sid=${sourceId}`, + url: `https://${host}/openrtb/?sid=${sourceId}`, data: { id: bidderRequest.auctionId, imp: imps, diff --git a/modules/dchain.js b/modules/dchain.js new file mode 100644 index 00000000000..6a1bd1ebf70 --- /dev/null +++ b/modules/dchain.js @@ -0,0 +1,149 @@ +import includes from 'core-js-pure/features/array/includes.js'; +import { config } from '../src/config.js'; +import { getHook } from '../src/hook.js'; +import { _each, isStr, isArray, isPlainObject, hasOwn, deepClone, deepAccess, logWarn, logError } from '../src/utils.js'; + +const shouldBeAString = ' should be a string'; +const shouldBeAnObject = ' should be an object'; +const shouldBeAnArray = ' should be an Array'; +const shouldBeValid = ' is not a valid dchain property'; +const MODE = { + STRICT: 'strict', + RELAXED: 'relaxed', + OFF: 'off' +}; +const MODES = []; // an array of modes +_each(MODE, mode => MODES.push(mode)); + +export function checkDchainSyntax(bid, mode) { + let dchainObj = deepClone(bid.meta.dchain); + let failPrefix = 'Detected something wrong in bid.meta.dchain object for bid:'; + let failMsg = ''; + const dchainPropList = ['ver', 'complete', 'nodes', 'ext']; + + function appendFailMsg(msg) { + failMsg += '\n' + msg; + } + + function printFailMsg() { + if (mode === MODE.STRICT) { + logError(failPrefix, bid, '\n', dchainObj, failMsg); + } else { + logWarn(failPrefix, bid, `\n`, dchainObj, failMsg); + } + } + + let dchainProps = Object.keys(dchainObj); + dchainProps.forEach(prop => { + if (!includes(dchainPropList, prop)) { + appendFailMsg(`dchain.${prop}` + shouldBeValid); + } + }); + + if (dchainObj.complete !== 0 && dchainObj.complete !== 1) { + appendFailMsg(`dchain.complete should be 0 or 1`); + } + + if (!isStr(dchainObj.ver)) { + appendFailMsg(`dchain.ver` + shouldBeAString); + } + + if (hasOwn(dchainObj, 'ext')) { + if (!isPlainObject(dchainObj.ext)) { + appendFailMsg(`dchain.ext` + shouldBeAnObject); + } + } + + if (!isArray(dchainObj.nodes)) { + appendFailMsg(`dchain.nodes` + shouldBeAnArray); + printFailMsg(); + if (mode === MODE.STRICT) return false; + } else { + const nodesPropList = ['asi', 'bsid', 'rid', 'name', 'domain', 'ext']; + dchainObj.nodes.forEach((node, index) => { + if (!isPlainObject(node)) { + appendFailMsg(`dchain.nodes[${index}]` + shouldBeAnObject); + } else { + let nodeProps = Object.keys(node); + nodeProps.forEach(prop => { + if (!includes(nodesPropList, prop)) { + appendFailMsg(`dchain.nodes[${index}].${prop}` + shouldBeValid); + } + + if (prop === 'ext') { + if (!isPlainObject(node.ext)) { + appendFailMsg(`dchain.nodes[${index}].ext` + shouldBeAnObject); + } + } else { + if (!isStr(node[prop])) { + appendFailMsg(`dchain.nodes[${index}].${prop}` + shouldBeAString); + } + } + }); + } + }); + } + + if (failMsg.length > 0) { + printFailMsg(); + if (mode === MODE.STRICT) { + return false; + } + } + return true; +} + +function isValidDchain(bid) { + let mode = MODE.STRICT; + const dchainConfig = config.getConfig('dchain'); + + if (dchainConfig && isStr(dchainConfig.validation) && MODES.indexOf(dchainConfig.validation) != -1) { + mode = dchainConfig.validation; + } + + if (mode === MODE.OFF) { + return true; + } else { + return checkDchainSyntax(bid, mode); + } +} + +export function addBidResponseHook(fn, adUnitCode, bid) { + const basicDchain = { + ver: '1.0', + complete: 0, + nodes: [] + }; + + if (deepAccess(bid, 'meta.networkId') && deepAccess(bid, 'meta.networkName')) { + basicDchain.nodes.push({ name: bid.meta.networkName, bsid: bid.meta.networkId.toString() }); + } + basicDchain.nodes.push({ name: bid.bidderCode }); + + let bidDchain = deepAccess(bid, 'meta.dchain'); + if (bidDchain && isPlainObject(bidDchain)) { + let result = isValidDchain(bid); + + if (result) { + // extra check in-case mode is OFF and there is a setup issue + if (isArray(bidDchain.nodes)) { + bid.meta.dchain.nodes.push({ asi: bid.bidderCode }); + } else { + logWarn('bid.meta.dchain.nodes did not exist or was not an array; did not append prebid dchain.', bid); + } + } else { + // remove invalid dchain + delete bid.meta.dchain; + } + } else { + bid.meta.dchain = basicDchain; + } + + fn(adUnitCode, bid); +} + +export function init() { + getHook('addBidResponse').before(addBidResponseHook, 35); +} + +init(); diff --git a/modules/dchain.md b/modules/dchain.md new file mode 100644 index 00000000000..f01b3483f3c --- /dev/null +++ b/modules/dchain.md @@ -0,0 +1,45 @@ +# dchain module + +Refer: +- https://iabtechlab.com/buyers-json-demand-chain/ + +## Sample code for dchain setConfig and dchain object +``` +pbjs.setConfig({ + "dchain": { + "validation": "strict" + } +}); +``` + +``` +bid.meta.dchain: { + "complete": 0, + "ver": "1.0", + "ext": {...}, + "nodes": [{ + "asi": "abc", + "bsid": "123", + "rid": "d4e5f6", + "name": "xyz", + "domain": "mno", + "ext": {...} + }, ...] +} +``` + +## Workflow +The dchain module is not enabled by default as it may not be necessary for all publishers. +If required, dchain module can be included as following +``` + $ gulp build --modules=dchain,pubmaticBidAdapter,openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter +``` + +The dchain module will validate a bidder's dchain object (if it was defined). Bidders should assign their dchain object into `bid.meta` field. If the dchain object is valid, it will remain in the bid object for later use. + +If it was not defined, the dchain will create a default dchain object for prebid. + +## Validation modes +- ```strict```: It is the default validation mode. In this mode, dchain object will not be accpeted from adapters if it is invalid. Errors are thrown for invalid dchain object. +- ```relaxed```: In this mode, errors are thrown for an invalid dchain object but the invalid dchain object is still accepted from adapters. +- ```off```: In this mode, no validations are performed and dchain object is accepted as is from adapters. \ No newline at end of file diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index d0c8eb29993..94167b92bb0 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -162,7 +162,7 @@ function buildImpression(bid) { impression = { id: bid.bidId, tagid: bid.params.tagId || '', - secure: window.location.protocol === 'https' ? 1 : 0, + secure: window.location.protocol === 'https:' ? 1 : 0, displaymanager: 'di_prebid', displaymanagerver: DI_M_V, ext: buildCustomParams(bid) diff --git a/modules/docereeBidAdapter.js b/modules/docereeBidAdapter.js index 704619f3ff7..737a9f707db 100644 --- a/modules/docereeBidAdapter.js +++ b/modules/docereeBidAdapter.js @@ -14,6 +14,13 @@ export const spec = { const { placementId } = bid.params; return !!placementId }, + isGdprConsentPresent: (bid) => { + const { gdpr, gdprConsent } = bid.params; + if (gdpr == '1') { + return !!gdprConsent + } + return true + }, buildRequests: (validBidRequests) => { const serverRequests = []; const { data } = config.getConfig('doceree.user') @@ -21,7 +28,7 @@ export const spec = { const encodedUserInfo = window.btoa(encodeURIComponent(JSON.stringify(data))) validBidRequests.forEach(function(validBidRequest) { - const { publisherUrl, placementId } = validBidRequest.params; + const { publisherUrl, placementId, gdpr, gdprConsent } = validBidRequest.params; const url = publisherUrl || page let queryString = ''; queryString = tryAppendQueryString(queryString, 'id', placementId); @@ -32,6 +39,8 @@ export const spec = { queryString = tryAppendQueryString(queryString, 'prebidjs', true); queryString = tryAppendQueryString(queryString, 'token', token); queryString = tryAppendQueryString(queryString, 'requestId', validBidRequest.bidId); + queryString = tryAppendQueryString(queryString, 'gdpr', gdpr); + queryString = tryAppendQueryString(queryString, 'gdpr_consent', gdprConsent); serverRequests.push({ method: 'GET', diff --git a/modules/docereeBidAdapter.md b/modules/docereeBidAdapter.md index d977e11f40a..9072fb374b6 100644 --- a/modules/docereeBidAdapter.md +++ b/modules/docereeBidAdapter.md @@ -25,6 +25,8 @@ var adUnits = [ params: { placementId: 'DOC_7jm9j5eqkl0xvc5w', //required publisherUrl: document.URL || window.location.href, //optional + gdpr: '1', //optional + gdprConsent:'CPQfU1jPQfU1jG0AAAENAwCAAAAAAAAAAAAAAAAAAAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g', //optional } } ] diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index 16c06073c41..09de5254745 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -12,7 +12,7 @@ const GVLID = 602; export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: ['dspx'], + aliases: [], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { return !!(bid.params.placement); diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index 3ab9af8e523..0ed23f11631 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -257,10 +257,18 @@ export const spec = { tagid, secure }; + + // adding gpid support + let gpid = deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot'); + if (!gpid) { + gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + } + if (gpid) { + data.ext = {gpid: gpid.toString()}; + } let typeSpecifics = isVideo ? { video: emxAdapter.buildVideo(bid) } : { banner: emxAdapter.buildBanner(bid) }; let bidfloorObj = bidfloor > 0 ? { bidfloor, bidfloorcur: DEFAULT_CUR } : {}; let emxBid = Object.assign(data, typeSpecifics, bidfloorObj); - emxImps.push(emxBid); }); diff --git a/modules/engageyaBidAdapter.js b/modules/engageyaBidAdapter.js index 27d1bb15af8..3e0f1d443b1 100644 --- a/modules/engageyaBidAdapter.js +++ b/modules/engageyaBidAdapter.js @@ -1,7 +1,4 @@ -import { - BANNER, - NATIVE -} from '../src/mediaTypes.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { createTrackPixelHtml } from '../src/utils.js'; const { @@ -10,14 +7,21 @@ const { const BIDDER_CODE = 'engageya'; const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json'; const ENDPOINT_METHOD = 'GET'; +const SUPPORTED_SIZES = [ + [100, 75], [236, 202], [100, 100], [130, 130], [200, 200], [250, 250], [300, 272], [300, 250], [300, 230], [300, 214], [300, 187], [300, 166], [300, 150], [300, 133], [300, 120], [400, 200], [300, 200], [250, 377], [620, 410], [207, 311], [310, 166], [310, 333], [190, 106], [228, 132], [300, 174], [80, 60], [600, 500], [600, 600], [1080, 610], [1080, 610], [624, 350], [650, 1168], [1080, 1920], [300, 374], [336, 280] +]; -function getPageUrl() { - var pUrl = window.location.href; - if (isInIframe()) { - pUrl = document.referrer ? document.referrer : pUrl; +function getPageUrl(bidRequest, bidderRequest) { + if (bidRequest.params.pageUrl && bidRequest.params.pageUrl != '[PAGE_URL]') { + return bidRequest.params.pageUrl; } - pUrl = encodeURIComponent(pUrl); - return pUrl; + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + return bidderRequest.refererInfo.referer; + } + const pageUrl = (isInIframe() && document.referrer) + ? document.referrer + : window.location.href; + return encodeURIComponent(pageUrl); } function isInIframe() { @@ -33,13 +37,14 @@ function getImageSrc(rec) { return rec.thumbnail_path.indexOf('http') === -1 ? 'https:' + rec.thumbnail_path : rec.thumbnail_path; } -function getImpressionTrackers(rec) { +function getImpressionTrackers(rec, response) { + const responseTrackers = [response.viewPxl]; if (!rec.trackers) { - return []; + return responseTrackers; } const impressionTrackers = rec.trackers.impressionPixels || []; const viewTrackers = rec.trackers.viewPixels || []; - return [...impressionTrackers, ...viewTrackers]; + return [...impressionTrackers, ...viewTrackers, ...responseTrackers]; } function parseNativeResponse(rec, response) { @@ -56,7 +61,7 @@ function parseNativeResponse(rec, response) { displayUrl: rec.url, cta: '', sponsoredBy: rec.displayName, - impressionTrackers: getImpressionTrackers(rec), + impressionTrackers: getImpressionTrackers(rec, response), }; } @@ -74,56 +79,65 @@ function parseBannerResponse(rec, response) { } const title = rec.title && rec.title.trim() ? `` : ''; const displayName = rec.displayName && title ? `` : ''; - const trackers = getImpressionTrackers(rec) + const trackers = getImpressionTrackers(rec, response) .map(createTrackPixelHtml) .join(''); return `${style}
${rec.title}${displayName}${title}${trackers}
`; } +function getImageSize(bidRequest) { + if (bidRequest.sizes && bidRequest.sizes.length > 0) { + return bidRequest.sizes[0]; + } else if (bidRequest.nativeParams && bidRequest.nativeParams.image && bidRequest.nativeParams.image.sizes) { + return bidRequest.nativeParams.image.sizes; + } + return [-1, -1]; +} + +function isValidSize([width, height]) { + if (!width || !height) { + return false; + } + return SUPPORTED_SIZES.some(([supportedWidth, supportedHeight]) => supportedWidth === width && supportedHeight === height); +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, NATIVE], - isBidRequestValid: function (bid) { - return bid && bid.params && bid.params.hasOwnProperty('widgetId') && bid.params.hasOwnProperty('websiteId') && !isNaN(bid.params.widgetId) && !isNaN(bid.params.websiteId); + + isBidRequestValid: function (bidRequest) { + return bidRequest && + bidRequest.params && + bidRequest.params.hasOwnProperty('widgetId') && + bidRequest.params.hasOwnProperty('websiteId') && + !isNaN(bidRequest.params.widgetId) && + !isNaN(bidRequest.params.websiteId) && + isValidSize(getImageSize(bidRequest)); }, buildRequests: function (validBidRequests, bidderRequest) { - var bidRequests = []; - if (validBidRequests && validBidRequests.length > 0) { - validBidRequests.forEach(function (bidRequest) { - if (bidRequest.params) { - var mediaType = bidRequest.hasOwnProperty('nativeParams') ? 1 : 2; - var imageWidth = -1; - var imageHeight = -1; - if (bidRequest.sizes && bidRequest.sizes.length > 0) { - imageWidth = bidRequest.sizes[0][0]; - imageHeight = bidRequest.sizes[0][1]; - } else if (bidRequest.nativeParams && bidRequest.nativeParams.image && bidRequest.nativeParams.image.sizes) { - imageWidth = bidRequest.nativeParams.image.sizes[0]; - imageHeight = bidRequest.nativeParams.image.sizes[1]; - } - - var widgetId = bidRequest.params.widgetId; - var websiteId = bidRequest.params.websiteId; - var pageUrl = (bidRequest.params.pageUrl && bidRequest.params.pageUrl != '[PAGE_URL]') ? bidRequest.params.pageUrl : ''; - if (!pageUrl) { - pageUrl = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : getPageUrl(); - } - var bidId = bidRequest.bidId; - var finalUrl = ENDPOINT_URL + '?pubid=0&webid=' + websiteId + '&wid=' + widgetId + '&url=' + pageUrl + '&ireqid=' + bidId + '&pbtpid=' + mediaType + '&imw=' + imageWidth + '&imh=' + imageHeight; - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprApplies && bidderRequest.consentString) { - finalUrl += '&is_gdpr=1&gdpr_consent=' + bidderRequest.consentString; - } - bidRequests.push({ - url: finalUrl, - method: ENDPOINT_METHOD, - data: '' - }); - } - }); + if (!validBidRequests) { + return []; } - - return bidRequests; + return validBidRequests.map(bidRequest => { + if (bidRequest.params) { + const mediaType = bidRequest.hasOwnProperty('nativeParams') ? 1 : 2; + const [imageWidth, imageHeight] = getImageSize(bidRequest); + const widgetId = bidRequest.params.widgetId; + const websiteId = bidRequest.params.websiteId; + const pageUrl = getPageUrl(bidRequest, bidderRequest); + const bidId = bidRequest.bidId; + let finalUrl = ENDPOINT_URL + '?pubid=0&webid=' + websiteId + '&wid=' + widgetId + '&url=' + pageUrl + '&ireqid=' + bidId + '&pbtpid=' + mediaType + '&imw=' + imageWidth + '&imh=' + imageHeight; + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprApplies && bidderRequest.consentString) { + finalUrl += '&is_gdpr=1&gdpr_consent=' + bidderRequest.consentString; + } + return { + url: finalUrl, + method: ENDPOINT_METHOD, + data: '' + }; + } + }).filter(Boolean); }, interpretResponse: function (serverResponse, bidRequest) { @@ -135,12 +149,12 @@ export const spec = { return response.recs.map(rec => { let bid = { requestId: response.ireqId, - cpm: rec.ecpm, width: response.imageWidth, height: response.imageHeight, creativeId: rec.postId, + cpm: rec.pecpm || (rec.ecpm / 100), currency: 'USD', - netRevenue: false, + netRevenue: !!rec.pecpm, ttl: 360, meta: { advertiserDomains: rec.domain ? [rec.domain] : [] }, } diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 492c35474ba..6fb39c49ec8 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -176,13 +176,13 @@ function isMediaTypesEmpty(mediaTypes) { * @return {FeedAdApiBidRequest} */ function createApiBidRParams(request) { - return { + return Object.assign({}, request.params, { ad_type: 0, client_token: request.params.clientToken, placement_id: request.params.placementId, sdk_version: `prebid_${VERSION}`, app_hybrid: false, - }; + }); } /** diff --git a/modules/feedadBidAdapter.md b/modules/feedadBidAdapter.md index 6f705df36b5..fc3537ec331 100644 --- a/modules/feedadBidAdapter.md +++ b/modules/feedadBidAdapter.md @@ -26,6 +26,7 @@ Prebid.JS adapter that connects to the FeedAd demand sources. params: { clientToken: 'your-client-token' // see below for more info placementId: 'your-placement-id' // see below for more info + decoration: 'decoration parameters' // optional, see below for info } } ] @@ -35,7 +36,8 @@ Prebid.JS adapter that connects to the FeedAd demand sources. # Required Parameters -| Parameter | Description | -| --------- | ----------- | +| Parameter | Description | +|---------------| ----------- | | `clientToken` | Your FeedAd web client token. You can view your client token inside the FeedAd admin panel. | | `placementId` | You can choose placement IDs yourself. A placement ID should be named after the ad position inside your product. For example, if you want to display an ad inside a list of news articles, you could name it "ad-news-overview".
A placement ID may consist of lowercase `a-z`, `0-9`, `-` and `_`. You do not have to manually create the placement IDs before using them. Just specify them within the code, and they will appear in the FeedAd admin panel after the first request.
[Learn more](/concept/feed_ad/index.html) about Placement IDs and how they are grouped to play the same Creative. | +| `decoration` | Optional. If you want to apply a [decoration](https://docs.feedad.com/web/feed_ad/#decorations) to the ad. diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js index de0e2c1312f..2aa86077991 100644 --- a/modules/fluctBidAdapter.js +++ b/modules/fluctBidAdapter.js @@ -1,6 +1,6 @@ import { _each, isEmpty } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import URLSearchParams from 'core-js-pure/web/url-search-params'; +import URLSearchParams from 'core-js-pure/web/url-search-params' const BIDDER_CODE = 'fluct'; const END_POINT = 'https://hb.adingo.jp/prebid'; diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 4798158bb3a..eca31dd5a95 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -312,6 +312,12 @@ export const spec = { requestParams._fw_us_privacy = bidderRequest.uspConsent; } + // Add schain object + var schain = currentBidRequest.schain; + if (schain) { + requestParams.schain = schain; + } + var vastParams = currentBidRequest.params.vastUrlParams; if (typeof vastParams === 'object') { for (var key in vastParams) { diff --git a/modules/futureads.md b/modules/futureads.md new file mode 100644 index 00000000000..7b1c1d55b7f --- /dev/null +++ b/modules/futureads.md @@ -0,0 +1,48 @@ +# 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/glimpseBidAdapter.js b/modules/glimpseBidAdapter.js index b3646755d80..64b987254e9 100644 --- a/modules/glimpseBidAdapter.js +++ b/modules/glimpseBidAdapter.js @@ -1,19 +1,22 @@ 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 storageManager = getStorageManager() +const GVLID = 1012 const BIDDER_CODE = 'glimpse' const ENDPOINT = 'https://api.glimpsevault.io/ads/serving/public/v1/prebid' const LOCAL_STORAGE_KEY = { - glimpse: { + vault: { jwt: 'gp_vault_jwt', }, } export const spec = { + gvlid: GVLID, code: BIDDER_CODE, supportedMediaTypes: [BANNER], @@ -37,20 +40,21 @@ export const spec = { * @returns {ServerRequest} */ buildRequests: (validBidRequests, bidderRequest) => { - const networkId = window.networkId || -1 - const bids = validBidRequests.map(processBidRequest) + const auth = getVaultJwt() const referer = getReferer(bidderRequest) const gdprConsent = getGdprConsentChoice(bidderRequest) - const jwt = getVaultJwt() + const bidRequests = validBidRequests.map(processBidRequest) + const firstPartyData = getFirstPartyData() const data = { - auth: jwt, + auth, data: { - bidderCode: spec.code, - networkId, - bids, referer, gdprConsent, + bidRequests, + site: firstPartyData.site, + user: firstPartyData.user, + bidderCode: spec.code, } } @@ -65,10 +69,9 @@ export const spec = { /** * Parse response from Glimpse server * @param bidResponse {ServerResponse} - * @param bidRequest {BidRequest} * @returns {Bid[]} */ - interpretResponse: (bidResponse, bidRequest) => { + interpretResponse: (bidResponse) => { const isValidResponse = isValidBidResponse(bidResponse) if (isValidResponse) { @@ -81,31 +84,12 @@ export const spec = { }, } -function processBidRequest(bidRequest) { - const sizes = normalizeSizes(bidRequest.sizes) - const keywords = bidRequest.params.keywords || [] - - return { - bidId: bidRequest.bidId, - placementId: bidRequest.params.placementId, - unitCode: bidRequest.adUnitCode, - sizes, - keywords, - } +function setVaultJwt(auth) { + storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.vault.jwt, auth) } -function normalizeSizes(sizes) { - const isSingleSize = - isArray(sizes) && - sizes.length === 2 && - !isArray(sizes[0]) && - !isArray(sizes[1]) - - if (isSingleSize) { - return [sizes] - } - - return sizes +function getVaultJwt() { + return storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.vault.jwt) || '' } function getReferer(bidderRequest) { @@ -127,7 +111,14 @@ function getGdprConsentChoice(bidderRequest) { hasValue(bidderRequest.gdprConsent) if (hasGdprConsent) { - return bidderRequest.gdprConsent + const gdprConsent = bidderRequest.gdprConsent + const hasGdprApplies = hasBooleanValue(gdprConsent.gdprApplies) + + return { + consentString: gdprConsent.consentString || '', + vendorData: gdprConsent.vendorData || {}, + gdprApplies: hasGdprApplies ? gdprConsent.gdprApplies : true, + } } return { @@ -137,12 +128,64 @@ function getGdprConsentChoice(bidderRequest) { } } -function setVaultJwt(auth) { - storageManager.setDataInLocalStorage(LOCAL_STORAGE_KEY.glimpse.jwt, auth) +function processBidRequest(bidRequest) { + const demand = bidRequest.params.demand || 'glimpse' + const sizes = normalizeSizes(bidRequest.sizes) + const keywords = bidRequest.params.keywords || {} + + return { + demand, + sizes, + keywords, + bidId: bidRequest.bidId, + placementId: bidRequest.params.placementId, + unitCode: bidRequest.adUnitCode, + } } -function getVaultJwt() { - return storageManager.getDataFromLocalStorage(LOCAL_STORAGE_KEY.glimpse.jwt) || '' +function normalizeSizes(sizes) { + const isSingleSize = + isArray(sizes) && + sizes.length === 2 && + !isArray(sizes[0]) && + !isArray(sizes[1]) + + if (isSingleSize) { + 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 } function isValidBidResponse(bidResponse) { @@ -162,6 +205,13 @@ function hasValue(value) { ) } +function hasBooleanValue(value) { + return ( + hasValue(value) && + typeof value === 'boolean' + ) +} + function hasStringValue(value) { return ( hasValue(value) && diff --git a/modules/gnetBidAdapter.js b/modules/gnetBidAdapter.js index f5e461afac8..8b0e953b2b6 100644 --- a/modules/gnetBidAdapter.js +++ b/modules/gnetBidAdapter.js @@ -1,9 +1,9 @@ -import { _each, parseSizesInput, isEmpty } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { _each, isEmpty, parseSizesInput } from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'gnet'; -const ENDPOINT = 'https://adserver.gnetproject.com/prebid.php'; +const ENDPOINT = 'https://service.gnetrtb.com/api/adrequest'; export const spec = { code: BIDDER_CODE, @@ -16,7 +16,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return !!(bid.params.websiteId); + return !!(bid.params.websiteId && bid.params.adunitId); }, /** diff --git a/modules/gnetBidAdapter.md b/modules/gnetBidAdapter.md index 447d00d8ff2..efab45a35b1 100644 --- a/modules/gnetBidAdapter.md +++ b/modules/gnetBidAdapter.md @@ -1,14 +1,14 @@ # Overview ``` -Module Name: Gnet Bidder Adapter +Module Name: Gnet RTB Bidder Adapter Module Type: Bidder Adapter -Maintainer: roberto.wu@grumft.com +Maintainer: bruno.bonanho@grumft.com ``` # Description -Connect to Gnet Project exchange for bids. +Connect to Gnet RTB exchange for bids. # Test Parameters ``` @@ -24,7 +24,7 @@ Connect to Gnet Project exchange for bids. { bidder: 'gnet', params: { - websiteId: '4' + websiteId: '1', adunitId: '1' } } ] diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js new file mode 100644 index 00000000000..8057925a62c --- /dev/null +++ b/modules/goldbachBidAdapter.js @@ -0,0 +1,1176 @@ +import { Renderer } from '../src/Renderer.js'; +import { + isEmpty, + convertCamelToUnderscore, + isFn, + createTrackPixelHtml, + convertTypes, + deepClone, + fill, + getParameterByName, + getMaxValueFromArray, + getMinValueFromArray, + chunk, + isArray, + isArrayOfNums, + isNumber, + isStr, + isPlainObject, + logError, + logInfo, + logMessage, + deepAccess, + getBidRequest, + transformBidderParamKeywords +} from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder, getIabSubCategory } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO, ADPOD } from '../src/mediaTypes.js'; +import { auctionManager } from '../src/auctionManager.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import { OUTSTREAM, INSTREAM } from '../src/video.js'; + +const BIDDER_CODE = 'goldbach'; +const URL = 'https://ib.adnxs.com/ut/v3/prebid'; +const PRICING_URL = 'https://templates.da-services.ch/01_universal/burda_prebid/1.0/json/sizeCPMMapping.json'; +const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; +const VIDEO_TARGETING = ['id', 'minduration', 'maxduration', + 'skippable', 'playback_method', 'frameworks', 'context', 'skipoffset']; +const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api']; +const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; +const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately +const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; +const DEFAULT_PRICE_MAPPING = { + '0x0': 2.5, + '300x600': 5, + '800x250': 6, + '350x600': 6 +}; +let PRICE_MAPPING; +const VIDEO_MAPPING = { + playback_method: { + 'unknown': 0, + 'auto_play_sound_on': 1, + 'auto_play_sound_off': 2, + 'click_to_play': 3, + 'mouse_over': 4, + 'auto_play_sound_unknown': 5 + }, + context: { + 'unknown': 0, + 'pre_roll': 1, + 'mid_roll': 2, + 'post_roll': 3, + 'outstream': 4, + 'in-banner': 5 + } +}; +const NATIVE_MAPPING = { + body: 'description', + body2: 'desc2', + cta: 'ctatext', + image: { + serverName: 'main_image', + requiredParams: { required: true } + }, + icon: { + serverName: 'icon', + requiredParams: { required: true } + }, + sponsoredBy: 'sponsored_by', + privacyLink: 'privacy_link', + salePrice: 'saleprice', + displayUrl: 'displayurl' +}; +const SOURCE = 'pbjs'; +const MAX_IMPS_PER_REQUEST = 15; +const mappingFileUrl = 'https://acdn.adnxs-simple.com/prebid/appnexus-mapping/mappings.json'; +const SCRIPT_TAG_START = ' { + if (Array.isArray(bid.params.placementId)) { + const ids = bid.params.placementId; + for (let i = 0; i < ids.length; i++) { + const newBid = Object.assign({}, bid, {params: {placementId: ids[i]}}); + localBidRequests.push(newBid) + } + } else { + localBidRequests.push(bid); + } + }); + const tags = localBidRequests.map(bidToTag); + const userObjBid = find(bidRequests, hasUserInfo); + let userObj = {}; + if (config.getConfig('coppa') === true) { + userObj = { 'coppa': true }; + } + if (userObjBid) { + Object.keys(userObjBid.params.user) + .filter(param => includes(USER_PARAMS, param)) + .forEach((param) => { + let uparam = convertCamelToUnderscore(param); + if (param === 'segments' && isArray(userObjBid.params.user[param])) { + let segs = []; + userObjBid.params.user[param].forEach(val => { + if (isNumber(val)) { + segs.push({'id': val}); + } else if (isPlainObject(val)) { + segs.push(val); + } + }); + userObj[uparam] = segs; + } else if (param !== 'segments') { + userObj[uparam] = userObjBid.params.user[param]; + } + }); + } + + const appDeviceObjBid = find(bidRequests, hasAppDeviceInfo); + let appDeviceObj; + if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) { + appDeviceObj = {}; + Object.keys(appDeviceObjBid.params.app) + .filter(param => includes(APP_DEVICE_PARAMS, param)) + .forEach(param => appDeviceObj[param] = appDeviceObjBid.params.app[param]); + } + + const appIdObjBid = find(bidRequests, hasAppId); + let appIdObj; + if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { + appIdObj = { + appid: appIdObjBid.params.app.id + }; + } + + let debugObj = {}; + let debugObjParams = {}; + const debugBidRequest = find(bidRequests, hasDebug); + if (debugBidRequest && debugBidRequest.debug) { + debugObj = debugBidRequest.debug; + } + + if (debugObj && debugObj.enabled) { + Object.keys(debugObj) + .filter(param => includes(DEBUG_PARAMS, param)) + .forEach(param => { + debugObjParams[param] = debugObj[param]; + }); + } + + const memberIdBid = find(bidRequests, hasMemberId); + const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; + const schain = bidRequests[0].schain; + const omidSupport = find(bidRequests, hasOmidSupport); + + const payload = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$' + }, + schain: schain + }; + + if (omidSupport) { + payload['iab_support'] = { + omidpn: 'Appnexus', + omidpv: '$prebid.version$' + } + } + + if (member > 0) { + payload.member_id = member; + } + + if (appDeviceObjBid) { + payload.device = appDeviceObj + } + if (appIdObjBid) { + payload.app = appIdObj; + } + + if (config.getConfig('adpod.brandCategoryExclusion')) { + payload.brand_category_uniqueness = true; + } + + if (debugObjParams.enabled) { + payload.debug = debugObjParams; + logInfo('Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); + } + + if (bidderRequest && bidderRequest.gdprConsent) { + // note - objects for impbus use underscore instead of camelCase + 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; + // pull only the ids from the string (after the ~) and convert them to an array of ints + 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 + } + + if (bidderRequest && bidderRequest.refererInfo) { + let refererinfo = { + rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + } + payload.referrer_detection = refererinfo; + } + + const hasAdPodBid = find(bidRequests, hasAdPod); + if (hasAdPodBid) { + bidRequests.filter(hasAdPod).forEach(adPodBid => { + const adPodTags = createAdPodRequest(tags, adPodBid); + // don't need the original adpod placement because it's in adPodTags + const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); + payload.tags = [...nonPodTags, ...adPodTags]; + }); + } + + 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); + addUserId(eids, deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); + addUserId(eids, deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2'); + + if (eids.length) { + payload.eids = eids; + } + } + + if (tags[0].publisher_id) { + payload.publisher_id = tags[0].publisher_id; + } + + const request = formatRequest(payload, bidderRequest); + // add pricing endpoint + return [{method: 'GET', url: PRICING_URL, options: {withCredentials: false}}, request]; + }, + + parseAndMapCpm: function(serverResponse) { + const responseBody = serverResponse.body; + if (Array.isArray(responseBody) && responseBody.length) { + let localData = {}; + responseBody.forEach(cpmPerSize => { + Object.keys(cpmPerSize).forEach(size => { + let obj = {}; + obj[size] = cpmPerSize[size]; + localData = Object.assign({}, localData, obj) + }) + }) + PRICE_MAPPING = localData; + return null; + } + + if (responseBody.version) { + const localPriceMapping = PRICE_MAPPING || DEFAULT_PRICE_MAPPING; + if (responseBody.tags && Array.isArray(responseBody.tags) && responseBody.tags.length) { + responseBody.tags.forEach((tag) => { + if (tag.ads && Array.isArray(tag.ads) && tag.ads.length) { + tag.ads.forEach(ad => { + if (ad.ad_type === 'banner') { + const size = `${ad.rtb.banner.width}x${ad.rtb.banner.height}`; + if (localPriceMapping[size]) { + ad.cpm = localPriceMapping[size]; + } else { + ad.cpm = localPriceMapping['0x0']; + } + } + }) + } + }); + } + } + return responseBody; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, { bidderRequest }) { + serverResponse = this.parseAndMapCpm(serverResponse); + if (!serverResponse) return []; + const bids = []; + if (serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter : ${serverResponse.error}`; + logError(errorMessage); + return bids; + } + + if (serverResponse.tags) { + serverResponse.tags.forEach(serverBid => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid) { + if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid, bidderRequest); + bid.mediaType = parseMediaType(rtbBid); + bids.push(bid); + } + } + }); + } + + if (serverResponse.debug && serverResponse.debug.debug_info) { + let debugHeader = 'AppNexus Debug Auction for Prebid\n\n' + let debugText = debugHeader + serverResponse.debug.debug_info + debugText = debugText + .replace(/(|)/gm, '\t') // Tables + .replace(/(<\/td>|<\/th>)/gm, '\n') // Tables + .replace(/^
/gm, '') // Remove leading
+ .replace(/(
\n|
)/gm, '\n') //
+ .replace(/

(.*)<\/h1>/gm, '\n\n===== $1 =====\n\n') // Header H1 + .replace(/(.*)<\/h[2-6]>/gm, '\n\n*** $1 ***\n\n') // Headers + .replace(/(<([^>]+)>)/igm, ''); // Remove any other tags + logMessage(debugText); + } + + return bids; + }, + + /** + * @typedef {Object} mappingFileInfo + * @property {string} url mapping file json url + * @property {number} refreshInDays prebid stores mapping data in localstorage so you can return in how many days you want to update value stored in localstorage. + * @property {string} localStorageKey unique key to store your mapping json in localstorage + */ + + /** + * Returns mapping file info. This info will be used by bidderFactory to preload mapping file and store data in local storage + * @returns {mappingFileInfo} + */ + getMappingFileInfo: function () { + return { + url: mappingFileUrl, + refreshInDays: 2 + } + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (syncOptions.iframeEnabled && hasPurpose1Consent({gdprConsent})) { + return [{ + type: 'iframe', + url: 'https://acdn.adnxs.com/dmp/async_usersync.html' + }]; + } + }, + + transformBidParams: function (params, isOpenRtb) { + params = convertTypes({ + 'member': 'string', + 'invCode': 'string', + 'placementId': 'number', + 'keywords': transformBidderParamKeywords, + 'publisherId': 'number' + }, params); + + if (isOpenRtb) { + params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; + if (params.usePaymentRule) { delete params.usePaymentRule; } + + if (isPopulatedArray(params.keywords)) { + params.keywords.forEach(deleteValues); + } + + Object.keys(params).forEach(paramKey => { + let convertedKey = convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + params[convertedKey] = params[paramKey]; + delete params[paramKey]; + } + }); + } + + return params; + }, + + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function (bid) { + if (bid.native) { + reloadViewabilityScriptWithCorrectParameters(bid); + } + } +} + +function isPopulatedArray(arr) { + return !!(isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +function reloadViewabilityScriptWithCorrectParameters(bid) { + let viewJsPayload = getAppnexusViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); + + if (viewJsPayload) { + let prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; + + let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload) + + let newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); + + // find iframe containing script tag + let frameArray = document.getElementsByTagName('iframe'); + + // boolean var to modify only one script. That way if there are muliple scripts, + // they won't all point to the same creative. + let modifiedAScript = false; + + // first, loop on all ifames + for (let i = 0; i < frameArray.length && !modifiedAScript; i++) { + let currentFrame = frameArray[i]; + try { + // IE-compatible, see https://stackoverflow.com/a/3999191/2112089 + let nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document; + + if (nestedDoc) { + // if the doc is present, we look for our jstracker + let scriptArray = nestedDoc.getElementsByTagName('script'); + for (let j = 0; j < scriptArray.length && !modifiedAScript; j++) { + let currentScript = scriptArray[j]; + if (currentScript.getAttribute('data-src') == jsTrackerSrc) { + currentScript.setAttribute('src', newJsTrackerSrc); + currentScript.setAttribute('data-src', ''); + if (currentScript.removeAttribute) { + currentScript.removeAttribute('data-src'); + } + modifiedAScript = true; + } + } + } + } catch (exception) { + // trying to access a cross-domain iframe raises a SecurityError + // this is expected and ignored + if (!(exception instanceof DOMException && exception.name === 'SecurityError')) { + // all other cases are raised again to be treated by the calling function + throw exception; + } + } + } + } +} + +function strIsAppnexusViewabilityScript(str) { + let regexMatchUrlStart = str.match(VIEWABILITY_URL_START); + let viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; + + let regexMatchFileName = str.match(VIEWABILITY_FILE_NAME); + let fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1; + + return str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr; +} + +function getAppnexusViewabilityScriptFromJsTrackers(jsTrackerArray) { + let viewJsPayload; + if (isStr(jsTrackerArray) && strIsAppnexusViewabilityScript(jsTrackerArray)) { + viewJsPayload = jsTrackerArray; + } else if (isArray(jsTrackerArray)) { + for (let i = 0; i < jsTrackerArray.length; i++) { + let currentJsTracker = jsTrackerArray[i]; + if (strIsAppnexusViewabilityScript(currentJsTracker)) { + viewJsPayload = currentJsTracker; + } + } + } + return viewJsPayload; +} + +function getViewabilityScriptUrlFromPayload(viewJsPayload) { + // extracting the content of the src attribute + // -> substring between src=" and " + let indexOfFirstQuote = viewJsPayload.indexOf('src="') + 5; // offset of 5: the length of 'src=' + 1 + let indexOfSecondQuote = viewJsPayload.indexOf('"', indexOfFirstQuote); + let jsTrackerSrc = viewJsPayload.substring(indexOfFirstQuote, indexOfSecondQuote); + return jsTrackerSrc; +} + +function hasPurpose1Consent(bidderRequest) { + let result = true; + if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies && bidderRequest.gdprConsent.apiVersion === 2) { + result = !!(deepAccess(bidderRequest.gdprConsent, 'vendorData.purpose.consents.1') === true); + } + } + return result; +} + +function formatRequest(payload, bidderRequest) { + let request = []; + let options = { + withCredentials: true + }; + + let endpointUrl = URL; + + if (!hasPurpose1Consent(bidderRequest)) { + endpointUrl = URL_SIMPLE; + } + + if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { + options.customHeaders = { + 'X-Is-Test': 1 + } + } + + if (payload.tags.length > MAX_IMPS_PER_REQUEST) { + const clonedPayload = deepClone(payload); + + chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { + clonedPayload.tags = tags; + const payloadString = JSON.stringify(clonedPayload); + request.push({ + method: 'POST', + url: endpointUrl, + data: payloadString, + bidderRequest, + options + }); + }); + } else { + const payloadString = JSON.stringify(payload); + request = { + method: 'POST', + url: endpointUrl, + data: payloadString, + bidderRequest, + options + }; + } + + return request; +} + +function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: rtbBid.renderer_id, + url: rtbBid.renderer_url, + config: rendererOptions, + loaded: false, + adUnitCode + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + logError('Prebid Error calling setRender on renderer', err); + } + + renderer.setEventHandlers({ + impression: () => logMessage('Outstream video impression event'), + loaded: () => logMessage('Outstream video loaded event'), + ended: () => { + logMessage('Outstream renderer video event'); + document.querySelector(`#${adUnitCode}`).style.display = 'none'; + } + }); + return renderer; +} + +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, rtbBid, bidderRequest) { + const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); + const bid = { + requestId: serverBid.uuid, + cpm: rtbBid.cpm, + creativeId: rtbBid.creative_id, + dealId: rtbBid.deal_id, + currency: 'USD', + netRevenue: true, + ttl: 300, + adUnitCode: bidRequest.adUnitCode, + appnexus: { + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code + } + }; + + // WE DON'T FULLY SUPPORT THIS ATM - future spot for adomain code; creating a stub for 5.0 compliance + if (rtbBid.adomain) { + bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [] }); + } + + if (rtbBid.advertiser_id) { + bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); + } + + if (rtbBid.rtb.video) { + // shared video properties used for all 3 contexts + Object.assign(bid, { + width: rtbBid.rtb.video.player_width, + height: rtbBid.rtb.video.player_height, + vastImpUrl: rtbBid.notify_url, + ttl: 3600 + }); + + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); + switch (videoContext) { + case ADPOD: + const primaryCatId = getIabSubCategory(bidRequest.bidder, rtbBid.brand_category_id); + bid.meta = Object.assign({}, bid.meta, { primaryCatId }); + const dealTier = rtbBid.deal_priority; + bid.video = { + context: ADPOD, + durationSeconds: Math.floor(rtbBid.rtb.video.duration_ms / 1000), + dealTier + }; + bid.vastUrl = rtbBid.rtb.video.asset_url; + break; + case OUTSTREAM: + bid.adResponse = serverBid; + bid.adResponse.ad = bid.adResponse.ads[0]; + bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; + bid.vastXml = rtbBid.rtb.video.content; + + if (rtbBid.renderer_url) { + const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid); + const rendererOptions = deepAccess(videoBid, 'renderer.options'); + bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); + } + break; + case INSTREAM: + bid.vastUrl = rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url); + break; + } + } else if (rtbBid.rtb[NATIVE]) { + const nativeAd = rtbBid.rtb[NATIVE]; + + // setting up the jsTracker: + // we put it as a data-src attribute so that the tracker isn't called + // until we have the adId (see onBidWon) + let jsTrackerDisarmed = rtbBid.viewability.config.replace('src=', 'data-src='); + + let jsTrackers = nativeAd.javascript_trackers; + + if (jsTrackers == undefined) { + jsTrackers = jsTrackerDisarmed; + } else if (isStr(jsTrackers)) { + jsTrackers = [jsTrackers, jsTrackerDisarmed]; + } else { + jsTrackers.push(jsTrackerDisarmed); + } + + bid[NATIVE] = { + title: nativeAd.title, + body: nativeAd.desc, + body2: nativeAd.desc2, + cta: nativeAd.ctatext, + rating: nativeAd.rating, + sponsoredBy: nativeAd.sponsored, + privacyLink: nativeAd.privacy_link, + address: nativeAd.address, + downloads: nativeAd.downloads, + likes: nativeAd.likes, + phone: nativeAd.phone, + price: nativeAd.price, + salePrice: nativeAd.saleprice, + clickUrl: nativeAd.link.url, + displayUrl: nativeAd.displayurl, + clickTrackers: nativeAd.link.click_trackers, + impressionTrackers: nativeAd.impression_trackers, + javascriptTrackers: jsTrackers + }; + if (nativeAd.main_img) { + bid['native'].image = { + url: nativeAd.main_img.url, + height: nativeAd.main_img.height, + width: nativeAd.main_img.width, + }; + } + if (nativeAd.icon) { + bid['native'].icon = { + url: nativeAd.icon.url, + height: nativeAd.icon.height, + width: nativeAd.icon.width, + }; + } + } else { + Object.assign(bid, { + width: rtbBid.rtb.banner.width, + height: rtbBid.rtb.banner.height, + ad: rtbBid.rtb.banner.content + }); + try { + if (rtbBid.rtb.trackers) { + const url = rtbBid.rtb.trackers[0].impression_urls[0]; + const tracker = createTrackPixelHtml(url); + bid.ad += tracker; + } + } catch (error) { + logError('Error appending tracking pixel', error); + } + } + + return bid; +} + +function bidToTag(bid) { + const tag = {}; + tag.sizes = transformSizes(bid.sizes); + tag.primary_size = tag.sizes[0]; + tag.ad_types = []; + tag.uuid = bid.bidId; + if (bid.params.placementId) { + tag.id = parseInt(bid.params.placementId, 10); + } else { + tag.code = bid.params.invCode; + } + tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; + tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.prebid = true; + tag.disable_psa = true; + let bidFloor = getBidFloor(bid); + if (bidFloor) { + tag.reserve = bidFloor; + } + if (bid.params.position) { + tag.position = { 'above': 1, 'below': 2 }[bid.params.position] || 0; + } + if (bid.params.trafficSourceCode) { + tag.traffic_source_code = bid.params.trafficSourceCode; + } + if (bid.params.privateSizes) { + tag.private_sizes = transformSizes(bid.params.privateSizes); + } + if (bid.params.supplyType) { + tag.supply_type = bid.params.supplyType; + } + if (bid.params.pubClick) { + tag.pubclick = bid.params.pubClick; + } + if (bid.params.extInvCode) { + tag.ext_inv_code = bid.params.extInvCode; + } + if (bid.params.publisherId) { + tag.publisher_id = parseInt(bid.params.publisherId, 10); + } + if (bid.params.externalImpId) { + tag.external_imp_id = bid.params.externalImpId; + } + if (!isEmpty(bid.params.keywords)) { + let keywords = transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; + } + + let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + tag.gpid = gpid; + } + + if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) { + tag.ad_types.push(NATIVE); + if (tag.sizes.length === 0) { + tag.sizes = transformSizes([1, 1]); + } + + if (bid.nativeParams) { + const nativeRequest = buildNativeRequest(bid.nativeParams); + tag[NATIVE] = { layouts: [nativeRequest] }; + } + } + + const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = deepAccess(bid, 'mediaTypes.video.context'); + + if (videoMediaType && context === 'adpod') { + tag.hb_source = 7; + } else { + tag.hb_source = 1; + } + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } + + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; + } + + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'context': + case 'playback_method': + let type = bid.params.video[param]; + type = (isArray(type)) ? type[0] : type; + tag.video[param] = VIDEO_MAPPING[param][type]; + break; + // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks + case 'frameworks': + break; + default: + tag.video[param] = bid.params.video[param]; + } + }); + + if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { + tag['video_frameworks'] = bid.params.video.frameworks; + } + } + + // use IAB ORTB values if the corresponding values weren't already set by bid.params.video + if (videoMediaType) { + tag.video = tag.video || {}; + Object.keys(videoMediaType) + .filter(param => includes(VIDEO_RTB_TARGETING, param)) + .forEach(param => { + switch (param) { + case 'minduration': + case 'maxduration': + if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; + break; + case 'skip': + if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); + break; + case 'skipafter': + if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; + break; + case 'playbackmethod': + if (typeof tag.video['playback_method'] !== 'number') { + let type = videoMediaType[param]; + type = (isArray(type)) ? type[0] : type; + + // we only support iab's options 1-4 at this time. + if (type >= 1 && type <= 4) { + tag.video['playback_method'] = type; + } + } + break; + case 'api': + if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { + // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) + let apiTmp = videoMediaType[param].map(val => { + let v = (val === 4) ? 5 : (val === 5) ? 4 : val; + + if (v >= 1 && v <= 5) { + return v; + } + }).filter(v => v); + tag['video_frameworks'] = apiTmp; + } + break; + } + }); + } + + if (bid.renderer) { + tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); + } + + if (bid.params.frameworks && isArray(bid.params.frameworks)) { + tag['banner_frameworks'] = bid.params.frameworks; + } + + let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); + if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { + tag.ad_types.push(BANNER); + } + + if (tag.ad_types.length === 0) { + delete tag.ad_types; + } + + return tag; +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if (isArray(requestSizes) && requestSizes.length === 2 && + !isArray(requestSizes[0])) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +function hasUserInfo(bid) { + return !!bid.params.user; +} + +function hasMemberId(bid) { + return !!parseInt(bid.params.member, 10); +} + +function hasAppDeviceInfo(bid) { + if (bid.params) { + return !!bid.params.app + } +} + +function hasAppId(bid) { + if (bid.params && bid.params.app) { + return !!bid.params.app.id + } + return !!bid.params.app +} + +function hasDebug(bid) { + return !!bid.debug +} + +function hasAdPod(bid) { + return ( + bid.mediaTypes && + bid.mediaTypes.video && + bid.mediaTypes.video.context === ADPOD + ); +} + +function hasOmidSupport(bid) { + let hasOmid = false; + const bidderParams = bid.params; + const videoParams = bid.params.video; + if (bidderParams.frameworks && isArray(bidderParams.frameworks)) { + hasOmid = includes(bid.params.frameworks, 6); + } + if (!hasOmid && videoParams && videoParams.frameworks && isArray(videoParams.frameworks)) { + hasOmid = includes(bid.params.video.frameworks, 6); + } + return hasOmid; +} + +/** + * Expand an adpod placement into a set of request objects according to the + * total adpod duration and the range of duration seconds. Sets minduration/ + * maxduration video property according to requireExactDuration configuration + */ +function createAdPodRequest(tags, adPodBid) { + const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; + + const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); + const maxDuration = getMaxValueFromArray(durationRangeSec); + + const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); + let request = fill(...tagToDuplicate, numberOfPlacements); + + if (requireExactDuration) { + const divider = Math.ceil(numberOfPlacements / durationRangeSec.length); + const chunked = chunk(request, divider); + + // each configured duration is set as min/maxduration for a subset of requests + durationRangeSec.forEach((duration, index) => { + chunked[index].map(tag => { + setVideoProperty(tag, 'minduration', duration); + setVideoProperty(tag, 'maxduration', duration); + }); + }); + } else { + // all maxdurations should be the same + request.map(tag => setVideoProperty(tag, 'maxduration', maxDuration)); + } + + return request; +} + +function getAdPodPlacementNumber(videoParams) { + const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; + const minAllowedDuration = getMinValueFromArray(durationRangeSec); + const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); + + return requireExactDuration + ? Math.max(numberOfPlacements, durationRangeSec.length) + : numberOfPlacements; +} + +function setVideoProperty(tag, key, value) { + if (isEmpty(tag.video)) { tag.video = {}; } + tag.video[key] = value; +} + +function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); +} + +function buildNativeRequest(params) { + const request = {}; + + // map standard prebid native asset identifier to /ut parameters + // e.g., tag specifies `body` but /ut only knows `description`. + // mapping may be in form {tag: ''} or + // {tag: {serverName: '', requiredParams: {...}}} + Object.keys(params).forEach(key => { + // check if one of the forms is used, otherwise + // a mapping wasn't specified so pass the key straight through + const requestKey = + (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || + NATIVE_MAPPING[key] || + key; + + // required params are always passed on request + const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; + request[requestKey] = Object.assign({}, requiredParams, params[key]); + + // convert the sizes of image/icon assets to proper format (if needed) + const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName); + if (isImageAsset && request[requestKey].sizes) { + let sizes = request[requestKey].sizes; + if (isArrayOfNums(sizes) || (isArray(sizes) && sizes.length > 0 && sizes.every(sz => isArrayOfNums(sz)))) { + request[requestKey].sizes = transformSizes(request[requestKey].sizes); + } + } + + if (requestKey === NATIVE_MAPPING.privacyLink) { + request.privacy_supported = true; + } + }); + + return request; +} + +/** + * This function hides google div container for outstream bids to remove unwanted space on page. Appnexus renderer creates a new iframe outside of google iframe to render the outstream creative. + * @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'); + } +} + +function hideSASIframe(elementId) { + try { + // find script tag with id 'sas_script'. This ensures it only works if you're using Smart Ad Server. + const el = document.getElementById(elementId).querySelectorAll("script[id^='sas_script']"); + if (el[0].nextSibling && el[0].nextSibling.localName === 'iframe') { + el[0].nextSibling.style.setProperty('display', 'none'); + } + } catch (e) { + // element not found! + } +} + +function outstreamRender(bid) { + hidedfpContainer(bid.adUnitCode); + hideSASIframe(bid.adUnitCode); + // push to render queue because ANOutstreamVideo may not be loaded yet + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + tagId: bid.adResponse.tag_id, + sizes: [bid.getSize().split('x')], + targetId: bid.adUnitCode, // target div id to render video + uuid: bid.adResponse.uuid, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig() + }, handleOutstreamRendererEvents.bind(null, bid)); + }); +} + +function handleOutstreamRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); +} + +function parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType === VIDEO) { + return VIDEO; + } else if (adType === NATIVE) { + return NATIVE; + } else { + return BANNER; + } +} + +function addUserId(eids, id, source, rti) { + if (id) { + if (rti) { + eids.push({ source, id, rti_partner: rti }); + } else { + eids.push({ source, id }); + } + } + return eids; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.reserve) ? bid.params.reserve : null; + } + + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + +registerBidder(spec); diff --git a/modules/goldbachBidAdapter.md b/modules/goldbachBidAdapter.md new file mode 100644 index 00000000000..f7c9479439b --- /dev/null +++ b/modules/goldbachBidAdapter.md @@ -0,0 +1,151 @@ +#Overview + +``` +Module Name: Goldbach Bid Adapter +Module Type: Bidder Adapter +Maintainer: dusan.veljovic@goldbach.com +``` + +# Description + +Connects to Xandr exchange for bids. + +Goldbach bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'goldbach', + params: { + placementId: 13144370 + } + }] + }, + // Native adUnit + { + code: 'native-div', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'goldbach', + params: { + placementId: 13232354, + allowSmallerSizes: true + } + }] + }, + // Video instream adUnit + { + code: 'video-instream', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + }, + }, + bids: [{ + goldbach: 'goldbach', + params: { + placementId: 13232361, + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'] + } + } + }] + }, + // Video outstream adUnit + { + code: 'video-outstream', + sizes: [[300, 250]], + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream', + // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. + // To note - goldbach supports additional values for our system that are not part of the ORTB spec. If you want + // to use these values, they will have to be declared in the bids[].params.video object instead using the goldbach syntax. + // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will + // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. + minduration: 1, + maxduration: 60, + skip: 0, // 1 - true, 0 - false + skipafter: 5, + playbackmethod: [2], // note - we only support options 1-4 at this time + api: [1,2,3] // note - option 6 is not supported at this time + } + }, + bids: [ + { + bidder: 'goldbach', + params: { + placementId: 13232385, + video: { + skippable: true, + playback_method: 'auto_play_sound_off' + } + } + } + ] + }, + // Banner adUnit in a App Webview + // Only use this for situations where prebid.js is in a webview of an App + // See Prebid Mobile for displaying ads via an SDK + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + } + bids: [{ + bidder: 'goldbach', + params: { + placementId: 13144370, + app: { + id: "B1O2W3M4AN.com.prebid.webview", + geo: { + lat: 40.0964439, + lng: -75.3009142 + }, + device_id: { + idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier + aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier + md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID + sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID + windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier + } + } + } + }] + } +]; +``` diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index 6519572b383..88f90419aeb 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -48,22 +48,48 @@ const sanitizeSlotPath = (path) => { return path; } +const defaultPreAuction = (adUnit, adServerAdSlot) => { + const context = adUnit.ortb2Imp.ext.data; + + // use pbadslot if supplied + if (context.pbadslot) { + return context.pbadslot; + } + + // confirm that GPT is set up + if (!isGptPubadsDefined()) { + return; + } + + // find all GPT slots with this name + var gptSlots = window.googletag.pubads().getSlots().filter(slot => slot.getAdUnitPath() === adServerAdSlot); + + if (gptSlots.length === 0) { + return; // should never happen + } + + if (gptSlots.length === 1) { + return adServerAdSlot; + } + + // else the adunit code must be div id. append it. + return `${adServerAdSlot}#${adUnit.code}`; +} + export const appendPbAdSlot = adUnit => { - adUnit.ortb2Imp = adUnit.ortb2Imp || {}; - adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; - adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; const context = adUnit.ortb2Imp.ext.data; const { customPbAdSlot } = _currentConfig; - if (customPbAdSlot) { - context.pbadslot = customPbAdSlot(adUnit.code, deepAccess(context, 'adserver.adslot')); + // use context.pbAdSlot if set (if someone set it already, it will take precedence over others) + if (context.pbadslot) { return; } - // use context.pbAdSlot if set - if (context.pbadslot) { + if (customPbAdSlot) { + context.pbadslot = customPbAdSlot(adUnit.code, deepAccess(context, 'adserver.adslot')); return; } + // use data attribute 'data-adslotid' if set try { const adUnitCodeDiv = document.getElementById(adUnit.code); @@ -78,12 +104,38 @@ export const appendPbAdSlot = adUnit => { return; } context.pbadslot = adUnit.code; + return true; }; export const makeBidRequestsHook = (fn, adUnits, ...args) => { appendGptSlots(adUnits); + const { useDefaultPreAuction, customPreAuction } = _currentConfig; adUnits.forEach(adUnit => { - appendPbAdSlot(adUnit); + // init the ortb2Imp if not done yet + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; + adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; + const context = adUnit.ortb2Imp.ext; + + // if neither new confs set do old stuff + if (!customPreAuction && !useDefaultPreAuction) { + const usedAdUnitCode = appendPbAdSlot(adUnit); + // gpid should be set to itself if already set, or to what pbadslot was (as long as it was not adUnit code) + if (!context.gpid && !usedAdUnitCode) { + context.gpid = context.data.pbadslot; + } + } else { + let adserverSlot = deepAccess(context, 'data.adserver.adslot'); + let result; + if (customPreAuction) { + result = customPreAuction(adUnit, adserverSlot); + } else if (useDefaultPreAuction) { + result = defaultPreAuction(adUnit, adserverSlot); + } + if (result) { + context.gpid = context.data.pbadslot = result; + } + } }); return fn.call(this, adUnits, ...args); }; @@ -94,6 +146,8 @@ const handleSetGptConfig = moduleConfig => { 'customGptSlotMatching', customGptSlotMatching => typeof customGptSlotMatching === 'function' && customGptSlotMatching, 'customPbAdSlot', customPbAdSlot => typeof customPbAdSlot === 'function' && customPbAdSlot, + 'customPreAuction', customPreAuction => typeof customPreAuction === 'function' && customPreAuction, + 'useDefaultPreAuction', useDefaultPreAuction => useDefaultPreAuction === true, ]); if (_currentConfig.enabled) { diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index f62b62b7a97..df1f4f3b6d7 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -29,7 +29,7 @@ let hasSynced = false; export const spec = { code: BIDDER_CODE, - aliases: ['playwire'], + aliases: ['playwire', 'adlivetech'], supportedMediaTypes: [ BANNER, VIDEO ], /** * Determines whether or not the given bid request is valid. @@ -291,10 +291,11 @@ export const spec = { if (!hasSynced && syncOptions.pixelEnabled) { let params = ''; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { + params += `&gdpr=${Number(gdprConsent.gdprApplies)}`; + } + if (typeof gdprConsent.consentString === 'string') { params += `&gdpr_consent=${gdprConsent.consentString}`; } } diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 8012afa2f30..244ac0c7c09 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -286,7 +286,8 @@ function buildRequests(validBidRequests, bidderRequest) { schain, transactionId, userId = {}, - ortb2Imp + ortb2Imp, + adUnitCode = '' } = bidRequest; const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); const eids = getEids(userId); @@ -295,12 +296,15 @@ function buildRequests(validBidRequests, bidderRequest) { let gpid = ''; const date = new Date(); - const lt = date && date.getTime(); - const to = date && date.getTimezoneOffset(); - if (to) { - lt && (data.lt = lt); - data.to = to; - } + const lt = date.getTime(); + const to = date.getTimezoneOffset(); + + // ADTS-174 Removed unnecessary checks to fix failing test + data.lt = lt; + data.to = to; + + // ADTS-169 add adUnitCode to requests + if (adUnitCode) data.aun = adUnitCode // ADTS-134 Retrieve ID envelopes for (const eid in eids) data[eid] = eids[eid]; diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 8ffe29e091f..11f8ffc5609 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -1,14 +1,14 @@ # ID5 Universal ID -The ID5 Universal ID is a shared, neutral identifier that publishers and ad tech platforms can use to recognise users even in environments where 3rd party cookies are not available. The ID5 Universal ID is designed to respect users' privacy choices and publishers’ preferences throughout the advertising value chain. For more information about the ID5 Universal ID and detailed integration docs, please visit [our documentation](https://support.id5.io/portal/en/kb/articles/prebid-js-user-id-module). We also recommend that you sign up for our [release notes](https://id5.io/universal-id/release-notes) to stay up-to-date with any changes to the implementation of the ID5 Universal ID in Prebid. +The ID5 ID is a shared, neutral identifier that publishers and ad tech platforms can use to recognise users even in environments where 3rd party cookies are not available. The ID5 ID is designed to respect users' privacy choices and publishers’ preferences throughout the advertising value chain. For more information about the ID5 ID and detailed integration docs, please visit [our documentation](https://support.id5.io/portal/en/kb/articles/prebid-js-user-id-module). -## ID5 Universal ID Registration +## ID5 ID Registration -The ID5 Universal ID is free to use, but requires a simple registration with ID5. Please visit [id5.io/universal-id](https://id5.io/universal-id) to sign up and request your ID5 Partner Number to get started. +The ID5 ID is free to use, but requires a simple registration with ID5. Please visit [our website](https://id5.io/solutions/#publishers) to sign up and request your ID5 Partner Number to get started. -The ID5 privacy policy is at [https://www.id5.io/platform-privacy-policy](https://www.id5.io/platform-privacy-policy). +The ID5 privacy policy is at [https://id5.io/platform-privacy-policy](https://id5.io/platform-privacy-policy). -## ID5 Universal ID Configuration +## ID5 ID Configuration First, make sure to add the ID5 submodule to your Prebid.js package with: @@ -46,7 +46,7 @@ pbjs.setConfig({ | Param under userSync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | -| params | Required | Object | Details for the ID5 Universal ID. | | +| params | Required | Object | Details for the ID5 ID. | | | params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | | params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://support.id5.io/portal/en/kb/articles/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | | params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | diff --git a/modules/idImportLibrary.js b/modules/idImportLibrary.js index e266f10cc4e..e1847edfce4 100644 --- a/modules/idImportLibrary.js +++ b/modules/idImportLibrary.js @@ -9,6 +9,7 @@ let conf; const LOG_PRE_FIX = 'ID-Library: '; const CONF_DEFAULT_OBSERVER_DEBOUNCE_MS = 250; const CONF_DEFAULT_FULL_BODY_SCAN = false; +const CONF_DEFAULT_INPUT_SCAN = false; const OBSERVER_CONFIG = { subtree: true, attributes: true, @@ -78,7 +79,13 @@ function targetAction(mutations, observer) { } } -function addInputElementsElementListner(conf) { +function addInputElementsElementListner() { + if (doesInputElementsHaveEmail()) { + _logInfo('Email found in input elements ' + email); + _logInfo('Post data on email found in target without'); + postData(); + return; + } _logInfo('Adding input element listeners'); const inputs = document.querySelectorAll('input[type=text], input[type=email]'); @@ -89,6 +96,19 @@ function addInputElementsElementListner(conf) { } } +function addFormInputElementsElementListner(id) { + _logInfo('Adding input element listeners'); + if (doesFormInputElementsHaveEmail(id)) { + _logInfo('Email found in input elements ' + email); + postData(); + return; + } + _logInfo('Adding input element listeners'); + const input = document.getElementById(id); + input.addEventListener('change', event => processInputChange(event)); + input.addEventListener('blur', event => processInputChange(event)); +} + function removeInputElementsElementListner() { _logInfo('Removing input element listeners'); const inputs = document.querySelectorAll('input[type=text], input[type=email]'); @@ -149,12 +169,6 @@ function handleTargetElement() { } function handleBodyElements() { - if (doesInputElementsHaveEmail()) { - _logInfo('Email found in input elements ' + email); - _logInfo('Post data on email found in target without'); - postData(); - return; - } email = getEmail(document.body.innerHTML); if (email !== null) { _logInfo('Email found in body ' + email); @@ -162,7 +176,7 @@ function handleBodyElements() { postData(); return; } - addInputElementsElementListner(); + if (conf.fullscan === true) { const bodyObserver = new MutationObserver(debounce(bodyAction, conf.debounce, false)); bodyObserver.observe(document.body, OBSERVER_CONFIG); @@ -182,6 +196,17 @@ function doesInputElementsHaveEmail() { return false; } +function doesFormInputElementsHaveEmail(formElementId) { + const input = document.getElementById(formElementId); + if (input) { + email = getEmail(input.value); + if (email !== null) { + return true; + } + } + return false; +} + function syncCallback() { return { success: function () { @@ -213,6 +238,10 @@ function associateIds() { if (window.MutationObserver || window.WebKitMutationObserver) { if (conf.target) { handleTargetElement(); + } else if (conf.formElementId) { + addFormInputElementsElementListner(conf.formElementId); + } else if (conf.inputscan) { + addInputElementsElementListner(); } else { handleBodyElements(); } @@ -236,6 +265,14 @@ export function setConfig(config) { config.fullscan = CONF_DEFAULT_FULL_BODY_SCAN; _logInfo('Set default fullscan ' + CONF_DEFAULT_FULL_BODY_SCAN); } + if (typeof config.inputscan !== 'boolean') { + config.inputscan = CONF_DEFAULT_INPUT_SCAN; + _logInfo('Set default input scan ' + CONF_DEFAULT_INPUT_SCAN); + } + + if (typeof config.formElementId == 'string') { + _logInfo('Looking for formElementId ' + config.formElementId); + } conf = config; associateIds(); } diff --git a/modules/idImportLibrary.md b/modules/idImportLibrary.md index 3dd78ee25d8..f91ca984bb3 100644 --- a/modules/idImportLibrary.md +++ b/modules/idImportLibrary.md @@ -8,6 +8,8 @@ | `url` | Yes | String | N/A | URL endpoint used to post the hashed email and user IDs. | | `debounce` | No | Number | 250 | Time in milliseconds before the email and IDs are fetched. | | `fullscan` | No | Boolean | false | Enable/disable a full page body scan to get email. | +| `formElementId` | No | String | N/A | ID attribute of the input (type=text/email) from which the email can be read. | +| `inputscan` | No | Boolean | N/A | Enable/disable a input element (type=text/email) scan to get email. | ## Example @@ -18,5 +20,7 @@ pbjs.setConfig({ url: 'https://example.com', debounce: 250, fullscan: false, + inputscan: false, + formElementId: "userid" }, }); diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js index db2c51ccf51..7ab19c0e2d6 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -37,6 +37,34 @@ function setImDataInCookie(value) { ); } +/** +* @param {string} bidderName +*/ +export function getBidderFunction(bidderName) { + const biddersFunction = { + ix: function (bid, data) { + if (data.im_segments && data.im_segments.length) { + config.setConfig({ + ix: {firstPartyData: {im_segments: data.im_segments}}, + }); + } + return bid + }, + pubmatic: function (bid, data) { + if (data.im_segments && data.im_segments.length) { + const dctr = deepAccess(bid, 'params.dctr'); + deepSetValue( + bid, + 'params.dctr', + `${dctr ? dctr + '|' : ''}im_segments=${data.im_segments.join(',')}` + ); + } + return bid + } + } + return biddersFunction[bidderName] || null; +} + export function getCustomBidderFunction(config, bidder) { const overwriteFn = deepAccess(config, `params.overwrites.${bidder}`) @@ -73,9 +101,12 @@ export function setRealTimeData(bidConfig, moduleConfig, data) { adUnits.forEach(adUnit => { adUnit.bids.forEach(bid => { + const bidderFunction = getBidderFunction(bid.bidder); const overwriteFunction = getCustomBidderFunction(moduleConfig, bid.bidder); if (overwriteFunction) { overwriteFunction(bid, data, utils, config); + } else if (bidderFunction) { + bidderFunction(bid, data); } }) }); diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 688a8815e93..1a523c0294f 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -1,4 +1,4 @@ -import { deepSetValue, logError, _each, getBidRequest, isNumber, isArray, deepAccess, isFn, isPlainObject, logWarn, getBidIdParameter, getUniqueIdentifierStr, isEmpty, isInteger } from '../src/utils.js'; +import { deepSetValue, logError, _each, getBidRequest, isNumber, isArray, deepAccess, isFn, isPlainObject, logWarn, getBidIdParameter, getUniqueIdentifierStr, isEmpty, isInteger, isStr } 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'; @@ -11,7 +11,7 @@ const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js const VIDEO_TARGETING = ['skip', 'skipmin', 'skipafter']; export const spec = { - version: '7.4.0', + version: '7.6.0', code: BIDDER_CODE, gvlid: 253, aliases: ['id'], @@ -45,8 +45,24 @@ export const spec = { libVersion: this.version }; - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { - requestParameters.gdpr = bidderRequest.gdprConsent.consentString; + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') + if (gdprConsent) { + // GDPR Consent String + if (gdprConsent.consentString) { + requestParameters.gdpr = 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); + deepSetValue( + requestParameters, + 'user.ext.consented_providers_settings.consented_providers', + atpIds.split('.').map(id => parseInt(id, 10)) + ); + } } if (bidderRequest && bidderRequest.uspConsent) { @@ -57,6 +73,22 @@ export const spec = { requestParameters.referrer = bidderRequest.refererInfo.referer; } + // 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) + }); + } + const genre = deepAccess(site, 'content.genre'); + if (genre && isStr(genre)) { + requestParameters.genre = genre; + } + } + // End of adding first party data + requestParameters.schain = bidRequests[0].schain; if (bidRequests[0].userId) { @@ -605,6 +637,12 @@ export function ImproveDigitalAdServerJSClient(endPoint) { 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; } diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 984d5c637bc..47a3353a897 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -1,8 +1,12 @@ -import {config} from '../src/config.js'; -import {BANNER} 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'; +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { + deepAccess, + generateUUID, + logError, +} from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; import find from 'core-js-pure/features/array/find.js'; const BIDDER_CODE = 'insticator'; @@ -50,12 +54,6 @@ function setUserId(userId) { function buildImpression(bidRequest) { const format = []; - const ext = { - insticator: { - adUnitId: bidRequest.params.adUnitId, - }, - } - const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; @@ -66,19 +64,17 @@ function buildImpression(bidRequest) { }); } - const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); - - if (gpid) { - ext.gpid = gpid; - } - return { id: bidRequest.bidId, tagid: bidRequest.adUnitCode, banner: { format, }, - ext, + ext: { + insticator: { + adUnitId: bidRequest.params.adUnitId, + }, + }, }; } @@ -125,24 +121,6 @@ function buildUser() { }; } -function extractSchain(bids, requestId) { - if (!bids || bids.length === 0 || !bids[0].schain) return; - - const schain = bids[0].schain; - if (schain && schain.nodes && schain.nodes.length && schain.nodes[0]) { - schain.nodes[0].rid = requestId; - } - - return schain; -} - -function extractEids(bids) { - if (!bids) return; - - const bid = find(bids, bid => isArray(bid.userIdAsEids) && bid.userIdAsEids.length > 0); - return bid ? bid.userIdAsEids : bids[0].userIdAsEids; -} - function buildRequest(validBidRequests, bidderRequest) { const req = { id: bidderRequest.bidderRequestId, @@ -160,50 +138,21 @@ function buildRequest(validBidRequests, bidderRequest) { regs: buildRegs(bidderRequest), user: buildUser(), imp: validBidRequests.map((bidRequest) => buildImpression(bidRequest)), - ext: { - insticator: { - adapter: { - vendor: 'prebid', - prebid: '$prebid.version$' - } - } - } }; const params = config.getConfig('insticator.params'); if (params) { req.ext = { - insticator: {...req.ext.insticator, ...params}, + insticator: params, }; } - const schain = extractSchain(validBidRequests, bidderRequest.bidderRequestId); - - if (schain) { - req.source.ext = {schain}; - } - - const eids = extractEids(validBidRequests); - - if (eids) { - req.user.ext = {eids}; - } - return req; } function buildBid(bid, bidderRequest) { const originalBid = find(bidderRequest.bids, (b) => b.bidId === bid.impid); - let meta = {}; - - if (bid.ext && bid.ext.meta) { - meta = bid.ext.meta; - } - - if (bid.adomain) { - meta.advertiserDomains = bid.adomain; - } return { requestId: bid.impid, @@ -217,7 +166,9 @@ function buildBid(bid, bidderRequest) { mediaType: 'banner', ad: bid.adm, adUnitCode: originalBid.adUnitCode, - ...(Object.keys(meta).length > 0 ? {meta} : {}) + meta: { + advertiserDomains: bid.bidADomain && bid.bidADomain.length ? bid.bidADomain : [] + }, }; } diff --git a/modules/instreamTracking.js b/modules/instreamTracking.js index 40a6ecb1939..ff8305c7fed 100644 --- a/modules/instreamTracking.js +++ b/modules/instreamTracking.js @@ -3,11 +3,11 @@ import { config } from '../src/config.js'; import { auctionManager } from '../src/auctionManager.js'; import { INSTREAM } from '../src/video.js'; import * as events from '../src/events.js'; -import { BID_STATUS, EVENTS, TARGETING_KEYS } from '../src/constants.json'; +import CONSTANTS from '../src/constants.json' -const {CACHE_ID, UUID} = TARGETING_KEYS; -const {BID_WON, AUCTION_END} = EVENTS; -const {RENDERED} = BID_STATUS; +const {CACHE_ID, UUID} = CONSTANTS.TARGETING_KEYS; +const {BID_WON, AUCTION_END} = CONSTANTS.EVENTS; +const {RENDERED} = CONSTANTS.BID_STATUS; const INSTREAM_TRACKING_DEFAULT_CONFIG = { enabled: false, diff --git a/modules/intersectionRtdProvider.js b/modules/intersectionRtdProvider.js new file mode 100644 index 00000000000..4404c4148fe --- /dev/null +++ b/modules/intersectionRtdProvider.js @@ -0,0 +1,114 @@ +import {submodule} from '../src/hook.js'; +import {isFn, logError} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import includes from 'core-js-pure/features/array/includes.js'; +import '../src/adapterManager.js'; +let observerAvailable = true; +function getIntersectionData(requestBidsObject, onDone, providerConfig, userConsent) { + const intersectionMap = {}; + const placeholdersMap = {}; + let done = false; + if (!observerAvailable) return complete(); + const observer = new IntersectionObserver(observerCallback, {threshold: 0.5}); + const adUnitCodes = requestBidsObject.adUnitCodes || []; + const auctionDelay = config.getConfig('realTimeData.auctionDelay') || 0; + const waitForIt = providerConfig.waitForIt; + let adUnits = requestBidsObject.adUnits || getGlobal().adUnits || []; + if (adUnitCodes.length) { + adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); + } + let checkTimeoutId; + findAndObservePlaceholders(); + if (auctionDelay > 0) { + setTimeout(complete, auctionDelay); + } + function findAndObservePlaceholders() { + const observed = adUnits.filter((unit) => { + const code = unit.code; + if (placeholdersMap[code]) return true; + const ph = document.getElementById(code); + if (ph) { + placeholdersMap[code] = ph; + observer.observe(ph); + return true; + } + }); + if ( + observed.length === adUnits.length || + !waitForIt || + auctionDelay <= 0 + ) { + return; + } + checkTimeoutId = setTimeout(findAndObservePlaceholders); + } + function observerCallback(entries) { + let entry = entries.pop(); + while (entry) { + const target = entry.target; + const id = target.getAttribute('id'); + if (id) { + const intersection = intersectionMap[id]; + if (!intersection || intersection.time < entry.time) { + intersectionMap[id] = { + 'boundingClientRect': cloneRect(entry.boundingClientRect), + 'intersectionRect': cloneRect(entry.intersectionRect), + 'rootRect': cloneRect(entry.rootRect), + 'intersectionRatio': entry.intersectionRatio, + 'isIntersecting': entry.isIntersecting, + 'time': entry.time + }; + if (adUnits.every(unit => !!intersectionMap[unit.code])) { + complete(); + } + } + } + entry = entries.pop(); + } + } + function complete() { + if (done) return; + if (checkTimeoutId) clearTimeout(checkTimeoutId); + done = true; + checkTimeoutId = null; + observer && observer.disconnect(); + adUnits && adUnits.forEach((unit) => { + const intersection = intersectionMap[unit.code]; + if (intersection && unit.bids) { + unit.bids.forEach(bid => bid.intersection = intersection); + } + }); + onDone(); + } +} +function init(moduleConfig) { + if (!isFn(window.IntersectionObserver)) { + logError('IntersectionObserver is not defined'); + observerAvailable = false; + } else { + observerAvailable = true; + } + return observerAvailable; +} +function cloneRect(rect) { + return rect ? { + 'left': rect.left, + 'top': rect.top, + 'right': rect.right, + 'bottom': rect.bottom, + 'width': rect.width, + 'height': rect.height, + 'x': rect.x, + 'y': rect.y, + } : rect; +} +export const intersectionSubmodule = { + name: 'intersection', + getBidRequestData: getIntersectionData, + init: init, +}; +function registerSubModule() { + submodule('realTimeData', intersectionSubmodule); +} +registerSubModule(); diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 75da1509f19..bc4387695c3 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -4,7 +4,8 @@ import {getStorageManager} from '../src/storageManager.js'; const CONSTANTS = { BIDDER_CODE: 'invibes', - BID_ENDPOINT: 'https://bid.videostep.com/Bid/VideoAdContent', + BID_ENDPOINT: '.videostep.com/Bid/VideoAdContent', + BID_SUBDOMAIN: 'https://bid', SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', @@ -77,13 +78,15 @@ function isBidRequestValid(bid) { function buildRequest(bidRequests, bidderRequest) { bidderRequest = bidderRequest || {}; const _placementIds = []; - let _loginId, _customEndpoint, _userId; + const _adUnitCodes = []; + let _customEndpoint, _userId, _domainId; let _ivAuctionStart = bidderRequest.auctionStart || Date.now(); bidRequests.forEach(function (bidRequest) { bidRequest.startTime = new Date().getTime(); _placementIds.push(bidRequest.params.placementId); - _loginId = _loginId || bidRequest.params.loginId; + _adUnitCodes.push(bidRequest.adUnitCode); + _domainId = _domainId || bidRequest.params.domainId; _customEndpoint = _customEndpoint || bidRequest.params.customEndpoint; _customUserSync = _customUserSync || bidRequest.params.customUserSync; _userId = _userId || bidRequest.userId; @@ -99,7 +102,7 @@ function buildRequest(bidRequests, bidderRequest) { let userIdModel = getUserIds(_userId); let bidParamsJson = { placementIds: _placementIds, - loginId: _loginId, + adUnitCodes: _adUnitCodes, auctionStartTime: _ivAuctionStart, bidVersion: CONSTANTS.PREBID_VERSION }; @@ -143,9 +146,11 @@ function buildRequest(bidRequests, bidderRequest) { } } + let endpoint = createEndpoint(_customEndpoint, _domainId, _placementIds); + return { method: CONSTANTS.METHOD, - url: _customEndpoint || CONSTANTS.BID_ENDPOINT, + url: endpoint, data: data, options: {withCredentials: true}, // for POST: { contentType: 'application/json', withCredentials: true } @@ -181,9 +186,12 @@ function handleResponse(responseObj, bidRequests) { const bidResponses = []; for (let i = 0; i < bidRequests.length; i++) { let bidRequest = bidRequests[i]; + let usedPlacementId = responseObj.UseAdUnitCode === true + ? bidRequest.params.placementId + '_' + bidRequest.adUnitCode + : bidRequest.params.placementId; - if (invibes.placementBids.indexOf(bidRequest.params.placementId) > -1) { - logInfo('Invibes Adapter - Placement was previously bid on ' + bidRequest.params.placementId); + if (invibes.placementBids.indexOf(usedPlacementId) > -1) { + logInfo('Invibes Adapter - Placement was previously bid on ' + usedPlacementId); continue; } @@ -191,21 +199,21 @@ function handleResponse(responseObj, bidRequests) { if (responseObj.AdPlacements != null) { for (let j = 0; j < responseObj.AdPlacements.length; j++) { let bidModel = responseObj.AdPlacements[j].BidModel; - if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { + if (bidModel != null && bidModel.PlacementId == usedPlacementId) { requestPlacement = responseObj.AdPlacements[j]; break; } } } else { let bidModel = responseObj.BidModel; - if (bidModel != null && bidModel.PlacementId == bidRequest.params.placementId) { + if (bidModel != null && bidModel.PlacementId == usedPlacementId) { requestPlacement = responseObj; } } - let bid = createBid(bidRequest, requestPlacement, responseObj.MultipositionEnabled); + let bid = createBid(bidRequest, requestPlacement, responseObj.MultipositionEnabled, usedPlacementId); if (bid !== null) { - invibes.placementBids.push(bidRequest.params.placementId); + invibes.placementBids.push(usedPlacementId); bidResponses.push(bid); } } @@ -213,9 +221,9 @@ function handleResponse(responseObj, bidRequests) { return bidResponses; } -function createBid(bidRequest, requestPlacement, multipositionEnabled) { +function createBid(bidRequest, requestPlacement, multipositionEnabled, usedPlacementId) { if (requestPlacement === null || requestPlacement.BidModel === null) { - logInfo('Invibes Adapter - Placement not configured for bidding ' + bidRequest.params.placementId); + logInfo('Invibes Adapter - Placement not configured for bidding ' + usedPlacementId); return null; } @@ -274,6 +282,48 @@ function createBid(bidRequest, requestPlacement, multipositionEnabled) { }; } +function createEndpoint(customEndpoint, domainId, placementIds) { + if (customEndpoint != null) { + return customEndpoint; + } + + if (domainId != null) { + return extractEndpointFromId(domainId - 1000); + } + + if (placementIds.length > 0) { + for (var i = 0; i < placementIds.length; i++) { + const id = extractFromPlacement(placementIds[i]); + if (id != null) { + return extractEndpointFromId(id); + } + } + } + + return extractEndpointFromId(1); +} + +function extractEndpointFromId(domainId) { + if (domainId < 2) { + return CONSTANTS.BID_SUBDOMAIN + CONSTANTS.BID_ENDPOINT; + } + + return CONSTANTS.BID_SUBDOMAIN + domainId + CONSTANTS.BID_ENDPOINT; +} + +function extractFromPlacement(placementId) { + if (placementId == null) { return null; } + + var pattern = /_ivbs([0-9]+)/g; + + var match = pattern.exec(placementId); + if (match != null && match[1] != null) { + return parseInt(match[1]); + } + + return null; +} + function addMeta(bidModelMeta) { var meta = {}; if (bidModelMeta != null) { @@ -684,7 +734,7 @@ let keywords = (function () { return kw; }()); -// ===================== +// ====================== export function resetInvibes() { invibes.optIn = undefined; diff --git a/modules/invibesBidAdapter.md b/modules/invibesBidAdapter.md index 06afba6344d..06588b6cb28 100644 --- a/modules/invibesBidAdapter.md +++ b/modules/invibesBidAdapter.md @@ -20,7 +20,8 @@ Connect to Invibes for bids. { bidder: 'invibes', params: { - placementId: '12345' + placementId: '12345', + domainId: 1001 } } ] diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 0a686ddace2..4e5606ce476 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -1,6 +1,9 @@ -import { deepAccess, parseGPTSingleSizeArray, inIframe, deepClone, logError, logWarn, isFn, contains, isInteger, isArray, deepSetValue, parseQueryStringParameters, isEmpty, mergeDeep, convertTypes } from '../src/utils.js'; +import { deepAccess, parseGPTSingleSizeArray, inIframe, deepClone, logError, logWarn, isFn, contains, isInteger, isArray, deepSetValue, parseQueryStringParameters, isEmpty, mergeDeep, convertTypes, hasDeviceAccess } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import CONSTANTS from '../src/constants.json'; +import { getStorageManager, validateStorageEnforcement } from '../src/storageManager.js'; +import events from '../src/events.js'; import find from 'core-js-pure/features/array/find.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { INSTREAM, OUTSTREAM } from '../src/video.js'; @@ -20,15 +23,23 @@ const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NET_REVENUE = true; const MAX_REQUEST_SIZE = 8000; const MAX_REQUEST_LIMIT = 4; - 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 FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; -// determines which eids we send and the rtiPartner field in ext - +export const ERROR_CODES = { + BID_SIZE_INVALID_FORMAT: 1, + BID_SIZE_NOT_INCLUDED: 2, + PROPERTY_NOT_INCLUDED: 3, + SITE_ID_INVALID_VALUE: 4, + BID_FLOOR_INVALID_FORMAT: 5, + IX_FPD_EXCEEDS_MAX_SIZE: 6, + EXCEEDS_MAX_SIZE: 7, + PB_FPD_EXCEEDS_MAX_SIZE: 8, + VIDEO_DURATION_INVALID: 9 +}; const FIRST_PARTY_DATA = { SITE: [ 'id', 'name', 'domain', 'cat', 'sectioncat', 'pagecat', 'page', 'ref', 'search', 'mobile', @@ -36,16 +47,19 @@ const FIRST_PARTY_DATA = { ], USER: ['id', 'buyeruid', 'yob', 'gender', 'keywords', 'customdata', 'geo', 'data', 'ext'] }; - const SOURCE_RTI_MAPPING = { 'liveramp.com': 'idl', 'netid.de': 'NETID', 'neustar.biz': 'fabrickId', 'zeotap.com': 'zeotapIdPlus', 'uidapi.com': 'UID2', - 'adserver.org': 'TDID' + 'adserver.org': 'TDID', + '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 + 'pubcid.org': '' // SharedID, pubcid }; - const PROVIDERS = [ 'britepoolid', 'id5id', @@ -60,11 +74,10 @@ const PROVIDERS = [ 'quantcastId', 'pubcid', 'TDID', - 'flocId' + 'flocId', + 'pubProvidedId' ]; - const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration']; // note: protocol/protocols is also reqd - const VIDEO_PARAMS_ALLOW_LIST = [ 'mimes', 'minduration', 'maxduration', 'protocols', 'protocol', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', @@ -73,6 +86,17 @@ const VIDEO_PARAMS_ALLOW_LIST = [ 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext', 'playerSize', 'w', 'h' ]; +const LOCAL_STORAGE_KEY = 'ixdiag'; +let hasRegisteredHandler = false; +export const storage = getStorageManager(GLOBAL_VENDOR_ID, BIDDER_CODE); + +// 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 = { + Banner: 1, + Video: 2, + Audio: 3, + Native: 4 +} /** * Transform valid bid request config object to banner impression object that will be sent to ad server. @@ -125,7 +149,10 @@ function bidToVideoImp(bid) { } if (imp.video.minduration > imp.video.maxduration) { - logError(`IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]`); + logError( + `IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]`, + { bidder: BIDDER_CODE, code: ERROR_CODES.VIDEO_DURATION_INVALID } + ); return {}; } @@ -173,7 +200,7 @@ function bidToImp(bid) { imp.id = bid.bidId; imp.ext = {}; - imp.ext.siteID = bid.params.siteId; + imp.ext.siteID = bid.params.siteId.toString(); if (bid.params.hasOwnProperty('id') && (typeof bid.params.id === 'string' || typeof bid.params.id === 'number')) { @@ -262,9 +289,14 @@ function parseBid(rawBid, currency, bidRequest) { bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; - // in the event of a video - if (deepAccess(rawBid, 'ext.vasturl')) { + if (rawBid.mtype == MEDIA_TYPES.Video) { + bid.vastXml = rawBid.adm + } else if (rawBid.ext && rawBid.ext.vasturl) { bid.vastUrl = rawBid.ext.vasturl + } + + // in the event of a video + if ((rawBid.ext && rawBid.ext.vasturl) || rawBid.mtype == MEDIA_TYPES.Video) { bid.width = bidRequest.video.w; bid.height = bidRequest.video.h; bid.mediaType = VIDEO; @@ -418,16 +450,19 @@ function getEidInfo(allEids, flocData) { let seenSources = {}; if (isArray(allEids)) { for (const eid of allEids) { - if (SOURCE_RTI_MAPPING[eid.source] && deepAccess(eid, 'uids.0')) { + if (SOURCE_RTI_MAPPING.hasOwnProperty(eid.source) && deepAccess(eid, 'uids.0')) { seenSources[eid.source] = true; - eid.uids[0].ext = { - rtiPartner: SOURCE_RTI_MAPPING[eid.source] - }; + if (SOURCE_RTI_MAPPING[eid.source] != '') { + eid.uids[0].ext = { + rtiPartner: SOURCE_RTI_MAPPING[eid.source] + }; + } delete eid.uids[0].atype; toSend.push(eid); } } } + const isValidFlocId = flocData && flocData.id && flocData.version; if (isValidFlocId) { const flocEid = { @@ -440,6 +475,7 @@ function getEidInfo(allEids, flocData) { return { toSend, seenSources }; } + /** * Builds a request object to be sent to the ad server based on bid requests. * @@ -498,6 +534,13 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r.ext.ixdiag[key] = ixdiag[key]; } + // Get cached errors stored in LocalStorage + const cachedErrors = getCachedErrors(); + + if (!isEmpty(cachedErrors)) { + r.ext.ixdiag.err = cachedErrors; + } + // if an schain is provided, send it along if (validBidRequests[0].schain) { r.source = { @@ -576,7 +619,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { const baseRequestSize = `${baseUrl}${parseQueryStringParameters({ ...payload, r: JSON.stringify(r) })}`.length; if (baseRequestSize > MAX_REQUEST_SIZE) { - logError('ix bidder: Base request size has exceeded maximum request size.'); + logError('IX Bid Adapter: Base request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.EXCEEDS_MAX_SIZE }); return requests; } @@ -606,7 +649,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { } currentRequestSize += fpdRequestSize; } else { - logError('ix bidder: IX config FPD request size has exceeded maximum request size.'); + logError('IX Bid Adapter: IX config FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE }); } } @@ -647,6 +690,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { if (impressionObjects.length && BANNER in impressionObjects[0]) { const { id, banner: { topframe }, ext } = impressionObjects[0]; + const gpid = impressions[transactionIds[adUnitIndex]].gpid; const _bannerImpression = { id, banner: { @@ -655,10 +699,10 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { }, } - if (ext.dfp_ad_unit_code) { - _bannerImpression.ext = { - dfp_ad_unit_code: ext.dfp_ad_unit_code - } + if (ext.dfp_ad_unit_code || gpid) { + _bannerImpression.ext = {}; + _bannerImpression.ext.dfp_ad_unit_code = ext.dfp_ad_unit_code; + _bannerImpression.ext.gpid = gpid; } if ('bidfloor' in impressionObjects[0]) { @@ -713,7 +757,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { const fpdRequestSize = encodeURIComponent(JSON.stringify({ ...site, ...user })).length; currentRequestSize += fpdRequestSize; } else { - logError('ix bidder: FPD request size has exceeded maximum request size.'); + logError('IX Bid Adapter: FPD request size has exceeded maximum request size.', { bidder: BIDDER_CODE, code: ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE }); } } @@ -878,6 +922,7 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { bannerImps[validBidRequest.transactionId].ixImps = [] } bannerImps[validBidRequest.transactionId].ixImps.push(imp); + bannerImps[validBidRequest.transactionId].gpid = deepAccess(validBidRequest, 'ortb2Imp.ext.gpid'); } if (ixConfig.hasOwnProperty('detectMissingSizes') && ixConfig.detectMissingSizes) { @@ -946,10 +991,117 @@ function createMissingBannerImp(bid, imp, newSize) { } /** + * @typedef {Array[message: string, err: Object]} ErrorData + * @property {string} message - The error message. + * @property {object} err - The error object. + * @property {string} err.bidder - The bidder of the error. + * @property {string} err.code - The error code. + */ + +/** + * Error Event handler that receives type and arguments in a data object. + * + * @param {ErrorData} data + */ +function storeErrorEventData(data) { + if (!storage.localStorageIsEnabled()) { + return; + } + + let currentStorage; + + try { + currentStorage = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_KEY) || '{}'); + } catch (e) { + logWarn('ix can not read ixdiag from localStorage.'); + } + + const todayDate = new Date(); + + Object.keys(currentStorage).map((errorDate) => { + const date = new Date(errorDate); + + if (date.setDate(date.getDate() + 7) - todayDate < 0) { + delete currentStorage[errorDate]; + } + }); + + if (data.type === 'ERROR' && data.arguments && data.arguments[1] && data.arguments[1].bidder === BIDDER_CODE) { + const todayString = todayDate.toISOString().slice(0, 10); + + const errorCode = data.arguments[1].code; + + if (errorCode) { + currentStorage[todayString] = currentStorage[todayString] || {}; + + if (!Number(currentStorage[todayString][errorCode])) { + currentStorage[todayString][errorCode] = 0; + } + + currentStorage[todayString][errorCode]++; + }; + } + + storage.setDataInLocalStorage(LOCAL_STORAGE_KEY, JSON.stringify(currentStorage)); +} + +/** + * Event handler for storing data into local storage. It will only store data if + * local storage premissions are avaliable + */ +function localStorageHandler(data) { + if (data.type === 'ERROR' && data.arguments && data.arguments[1] && data.arguments[1].bidder === BIDDER_CODE) { + const DEFAULT_ENFORCEMENT_SETTINGS = { + hasEnforcementHook: false, + valid: hasDeviceAccess() + }; + validateStorageEnforcement(GLOBAL_VENDOR_ID, BIDDER_CODE, DEFAULT_ENFORCEMENT_SETTINGS, (permissions) => { + if (permissions.valid) { + storeErrorEventData(data); + } + }); + } +} + +/** + * Get ixdiag stored in LocalStorage and format to be added to request payload + * + * @returns {Object} Object with error codes and counts + */ +function getCachedErrors() { + if (!storage.localStorageIsEnabled()) { + return; + } + + const errors = {}; + let currentStorage; + + try { + currentStorage = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_KEY) || '{}'); + } catch (e) { + logError('ix can not read ixdiag from localStorage.'); + return null; + } + + Object.keys(currentStorage).forEach((date) => { + Object.keys(currentStorage[date]).forEach((code) => { + if (typeof currentStorage[date][code] === 'number') { + errors[code] = errors[code] + ? errors[code] + currentStorage[date][code] + : currentStorage[date][code]; + } + }); + }); + + return errors; +} + +/** + * * Initialize Outstream Renderer * @param {Object} bid */ -function outstreamRenderer (bid) { +function outstreamRenderer(bid) { bid.renderer.push(() => { var config = { width: bid.width, @@ -957,7 +1109,13 @@ function outstreamRenderer (bid) { timeout: 3000 }; - window.IXOutstreamPlayer(bid.vastUrl, bid.adUnitCode, config); + // 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); + } }); } @@ -966,7 +1124,7 @@ function outstreamRenderer (bid) { * @param {string} id * @returns {Renderer} */ -function createRenderer (id) { +function createRenderer(id) { const renderer = Renderer.install({ id: id, url: RENDERER_URL, @@ -1000,6 +1158,12 @@ export const spec = { * @return {boolean} True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { + if (!hasRegisteredHandler) { + events.on(CONSTANTS.EVENTS.AUCTION_DEBUG, localStorageHandler); + events.on(CONSTANTS.EVENTS.AD_RENDER_FAILED, localStorageHandler); + hasRegisteredHandler = true; + } + const paramsVideoRef = deepAccess(bid, 'params.video'); const paramsSize = deepAccess(bid, 'params.size'); const mediaTypeBannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); @@ -1009,6 +1173,7 @@ export const spec = { const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); if (bid.hasOwnProperty('mediaType') && !(contains(SUPPORTED_AD_TYPES, bid.mediaType))) { + logWarn('IX Bid Adapter: media type is not supported.'); return false; } @@ -1020,26 +1185,31 @@ export const spec = { // since there is an ix bidder level size, make sure its valid const ixSize = getFirstSize(paramsSize); if (!ixSize) { - logError('ix bidder params: size has invalid format.'); + logError('IX Bid Adapter: size has invalid format.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_SIZE_INVALID_FORMAT }); return false; } // check if the ix bidder level size, is present in ad unit level if (!includesSize(bid.sizes, ixSize) && !(includesSize(mediaTypeVideoPlayerSize, ixSize)) && !(includesSize(mediaTypeBannerSizes, ixSize))) { - logError('ix bidder params: bid size is not included in ad unit sizes or player size.'); + logError('IX Bid Adapter: bid size is not included in ad unit sizes or player size.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_SIZE_NOT_INCLUDED }); return false; } } if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') { - logError('ix bidder params: siteId must be string or number value.'); + logError('IX Bid Adapter: siteId must be string or number type.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); + return false; + } + + if (typeof bid.params.siteId !== 'string' && isNaN(Number(bid.params.siteId))) { + logError('IX Bid Adapter: siteId must valid value', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); return false; } if (hasBidFloor || hasBidFloorCur) { if (!(hasBidFloor && hasBidFloorCur && isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur))) { - logError('ix bidder params: bidFloor / bidFloorCur parameter has invalid format.'); + logError('IX Bid Adapter: bidFloor / bidFloorCur parameter has invalid format.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_FLOOR_INVALID_FORMAT }); return false; } } @@ -1048,7 +1218,7 @@ export const spec = { const errorList = checkVideoParams(mediaTypeVideoRef, paramsVideoRef); if (errorList.length) { errorList.forEach((err) => { - logError(err); + logError(err, { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); }); return false; } @@ -1169,6 +1339,16 @@ export const spec = { bids.push(bid); } + + if (deepAccess(requestBid, 'ext.ixdiag.err')) { + if (storage.localStorageIsEnabled()) { + try { + storage.removeDataFromLocalStorage(LOCAL_STORAGE_KEY); + } catch (e) { + logError('ix can not clear ixdiag from localStorage.'); + } + } + } } return bids; diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 8332e720ae7..5a7e9f13686 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -155,8 +155,10 @@ export function enrichAdUnits(adUnits) { if (!vat) { return; } + const contentId = getContentId(vat.mediaID); + const contentData = getContentData(vat.segments); const targeting = formatTargetingResponse(vat); - addTargetingToBids(adUnit.bids, targeting); + enrichBids(adUnit.bids, targeting, contentId, contentData); }; loadVat(jwTargeting, onVatResponse); }); @@ -235,6 +237,9 @@ export function getVatFromPlayer(playerID, mediaID) { }; } +/* + deprecated + */ export function formatTargetingResponse(vat) { const { segments, mediaID } = vat; const targeting = {}; @@ -243,23 +248,83 @@ export function formatTargetingResponse(vat) { } if (mediaID) { - const id = 'jw_' + mediaID; targeting.content = { - id + id: getContentId(mediaID) } } return targeting; } -function addTargetingToBids(bids, targeting) { - if (!bids || !targeting) { +export function getContentId(mediaID) { + if (!mediaID) { + return; + } + + return 'jw_' + mediaID; +} + +export function getContentData(segments) { + if (!segments || !segments.length) { + return; + } + + const formattedSegments = segments.reduce((convertedSegments, rawSegment) => { + convertedSegments.push({ + id: rawSegment, + value: rawSegment + }); + return convertedSegments; + }, []); + + return { + name: 'jwplayer', + ext: { + segtax: 502 + }, + segment: formattedSegments + }; +} + +export function addOrtbSiteContent(bid, contentId, contentData) { + if (!contentId && !contentData) { return; } - bids.forEach(bid => addTargetingToBid(bid, targeting)); + let ortb2 = bid.ortb2 || {}; + let site = ortb2.site = ortb2.site || {}; + let content = site.content = site.content || {}; + + if (contentId) { + content.id = contentId; + } + + if (contentData) { + const data = content.data = content.data || []; + data.push(contentData); + } + + bid.ortb2 = ortb2; } +function enrichBids(bids, targeting, contentId, contentData) { + if (!bids) { + return; + } + + bids.forEach(bid => { + addTargetingToBid(bid, targeting); + addOrtbSiteContent(bid, contentId, contentData); + }); +} + +/* + deprecated + */ export function addTargetingToBid(bid, targeting) { + if (!targeting) { + return; + } + const rtd = bid.rtd || {}; const jwRtd = {}; jwRtd[SUBMODULE_NAME] = Object.assign({}, rtd[SUBMODULE_NAME], { targeting }); diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 7fb1bb13d74..77f65909040 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -81,20 +81,30 @@ realTimeData = { # Usage for Bid Adapters: Implement the `buildRequests` function. When it is called, the `bidRequests` param will be an array of bids. -Each bid for which targeting information was found will conform to the following object structure: +Each bid for which targeting information was found will have a ortb2 param conforming to the [oRTB v2 object structure](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf). The `ortb2` object will contain our proprietaty targeting segments in a format compliant with the [IAB's segment taxonomy structure](https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/segtax.md). + +Example: ```javascript { adUnitCode: 'xyz', bidId: 'abc', ..., - rtd: { - jwplayer: { - targeting: { - segments: ['123', '456'], - content: { - id: 'jw_abc123' - } + ortb2: { + site: { + content: { + id: 'jw_abc123', + data: [{ + name: 'jwplayer', + ext: { + segtax: 502 + }, + segment: [{ + id: '123' + }, { + id: '456' + }] + }] } } } @@ -102,9 +112,15 @@ Each bid for which targeting information was found will conform to the following ``` where: -- `segments` is an array of jwpseg targeting segments, of type string. -- `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. +- `ortb2` is an object containing first party data + - `site` is an object containing page specific information + - `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 + - `ext.segtax` whose `502` value is the unique identifier for JW Player's proprietary taxonomy + - `segment` is an array containing the segment taxonomy values as an object where: + - `id` is the string representation of the data segment value. **Example:** diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 7bb8e580f2a..ff56c97e7b7 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -73,8 +73,8 @@ export const spec = { let meta; if (adUnit.metadata && adUnit.metadata.landingPageDomain) { meta = { - clickUrl: adUnit.metadata.landingPageDomain, - advertiserDomains: [adUnit.metadata.landingPageDomain] + clickUrl: adUnit.metadata.landingPageDomain[0], + advertiserDomains: adUnit.metadata.landingPageDomain }; } bidResponses.push({ diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index fad4b6b96b3..b04b2124dd8 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -158,7 +158,9 @@ function buildPlacement(bidRequest) { height: size[1] } }), - type: bidRequest.params.adUnitType.toUpperCase() + type: bidRequest.params.adUnitType.toUpperCase(), + publisherId: bidRequest.params.publisherId, + userIdAsEids: bidRequest.userIdAsEids } } } diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index 5ef109aef96..25b919956e0 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -114,6 +114,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE let wonBid = cache.auctions[args.auctionId].bids[args.requestId]; wonBid.won = true; wonBid.floorData = args.floorData; + wonBid.rUp = args.rUp; if (wonBid.sendStatus != 0) { livewrappedAnalyticsAdapter.sendEvents(); } @@ -288,7 +289,8 @@ function getWins(gdpr, auctionIds) { auctionId: auctionIdPos, auc: bid.auc, buc: bid.buc, - lw: bid.lw + lw: bid.lw, + rUp: bid.rUp }); } }); diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 84b80ac14d4..6b7c055b295 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -1,4 +1,4 @@ -import { isSafariBrowser, deepAccess, getWindowTop } from '../src/utils.js'; +import { isSafariBrowser, deepAccess, getWindowTop, mergeDeep } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import find from 'core-js-pure/features/array/find.js'; @@ -60,11 +60,17 @@ export const spec = { const bundle = find(bidRequests, hasBundleParam); const tid = find(bidRequests, hasTidParam); const schain = bidRequests[0].schain; + let ortb2 = config.getConfig('ortb2'); + const eids = handleEids(bidRequests); bidUrl = bidUrl ? bidUrl.params.bidUrl : URL; url = url ? url.params.url : (getAppDomain() || getTopWindowLocation(bidderRequest)); test = test ? test.params.test : undefined; var adRequests = bidRequests.map(bidToAdRequest); + if (eids) { + ortb2 = mergeDeep(ortb2 || {}, eids); + } + const payload = { auctionId: auctionId ? auctionId.auctionId : undefined, publisherId: publisherId ? publisherId.params.publisherId : undefined, @@ -86,7 +92,7 @@ export const spec = { cookieSupport: !isSafariBrowser() && storage.cookiesAreEnabled(), rcv: getAdblockerRecovered(), adRequests: [...adRequests], - rtbData: handleEids(bidRequests), + rtbData: ortb2, schain: schain }; diff --git a/modules/loglyliftBidAdapter.js b/modules/loglyliftBidAdapter.js new file mode 100644 index 00000000000..e1319d08766 --- /dev/null +++ b/modules/loglyliftBidAdapter.js @@ -0,0 +1,79 @@ +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { 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], + + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.adspotId); + }, + + buildRequests: function (bidRequests, bidderRequest) { + const requests = []; + for (let i = 0, len = bidRequests.length; i < len; i++) { + const request = { + method: 'POST', + url: ENDPOINT_URL + '?adspot_id=' + bidRequests[i].params.adspotId, + data: JSON.stringify(newBidRequest(bidRequests[i], bidderRequest)), + options: {}, + bidderRequest + }; + requests.push(request); + } + return requests; + }, + + interpretResponse: function (serverResponse, { bidderRequest }) { + serverResponse = serverResponse.body; + const bidResponses = []; + if (!serverResponse || serverResponse.error) { + return bidResponses; + } + serverResponse.bids.forEach(function (bid) { + bidResponses.push(bid); + }) + return bidResponses; + }, + + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + + if (syncOptions.iframeEnabled && serverResponses.length > 0) { + syncs.push({ + type: 'iframe', + url: 'https://sync.logly.co.jp/sync/sync.html' + }); + } + return syncs; + } + +}; + +function newBidRequest(bid, bidderRequest) { + const currencyObj = config.getConfig('currency'); + const currency = (currencyObj && currencyObj.adServerCurrency) || 'USD'; + + return { + auctionId: bid.auctionId, + bidderRequestId: bid.bidderRequestId, + transactionId: bid.transactionId, + adUnitCode: bid.adUnitCode, + bidId: bid.bidId, + mediaTypes: bid.mediaTypes, + params: bid.params, + prebidJsVersion: '$prebid.version$', + url: window.location.href, + domain: config.getConfig('publisherDomain'), + referer: bidderRequest.refererInfo.referer, + auctionStartTime: bidderRequest.auctionStart, + currency: currency, + timeout: config.getConfig('bidderTimeout') + }; +} + +registerBidder(spec); diff --git a/modules/loglyliftBidAdapter.md b/modules/loglyliftBidAdapter.md new file mode 100644 index 00000000000..9bca238b03e --- /dev/null +++ b/modules/loglyliftBidAdapter.md @@ -0,0 +1,55 @@ +# Overview +``` +Module Name: LOGLY lift for Publisher +Module Type: Bidder Adapter +Maintainer: dev@logly.co.jp +``` + +# Description +Module that connects to Logly's demand sources. +Currently module supports only native mediaType. + +# Test Parameters +``` +var adUnits = [ + // Native adUnit + { + code: 'test-native-code', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'loglylift', + params: { + adspotId: 4302078 + } + }] + } +]; +``` + +# UserSync example + +``` +pbjs.setConfig({ + userSync: { + filterSettings: { + iframe: { + bidders: '*', // '*' represents all bidders + filter: 'include' + } + } + } +}); +``` diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 9abebb5533c..82503a57e9e 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -4,10 +4,20 @@ * @module modules/lotamePanoramaId * @requires module:modules/userId */ -import { timestamp, isStr, logError, isBoolean, buildUrl, isEmpty, isArray } from '../src/utils.js'; +import { + timestamp, + isStr, + logError, + isBoolean, + buildUrl, + isEmpty, + isArray, + isEmptyStr +} 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 KEY_ID = 'panoramaId'; const KEY_EXPIRY = `${KEY_ID}_expiry`; @@ -115,14 +125,23 @@ function saveLotameCache( /** * Retrieve all the cached values from cookies and/or local storage + * @param {Number} clientId */ -function getLotameLocalCache() { +function getLotameLocalCache(clientId = undefined) { let cache = { data: getFromStorage(KEY_ID), expiryTimestampMs: 0, + clientExpiryTimestampMs: 0, }; try { + if (clientId) { + const rawClientExpiry = getFromStorage(`${KEY_EXPIRY}_${clientId}`); + if (isStr(rawClientExpiry)) { + cache.clientExpiryTimestampMs = parseInt(rawClientExpiry, 10); + } + } + const rawExpiry = getFromStorage(KEY_EXPIRY); if (isStr(rawExpiry)) { cache.expiryTimestampMs = parseInt(rawExpiry, 10); @@ -191,11 +210,25 @@ export const lotamePanoramaIdSubmodule = { */ getId(config, consentData, cacheIdObj) { cookieDomain = lotamePanoramaIdSubmodule.findRootDomain(); - let localCache = getLotameLocalCache(); + const configParams = (config && config.params) || {}; + const clientId = configParams.clientId; + const hasCustomClientId = !isEmpty(clientId); + const localCache = getLotameLocalCache(clientId); - let refreshNeeded = Date.now() > localCache.expiryTimestampMs; + const hasExpiredPanoId = Date.now() > localCache.expiryTimestampMs; + + if (hasCustomClientId) { + const hasFreshClientNoConsent = Date.now() < localCache.clientExpiryTimestampMs; + if (hasFreshClientNoConsent) { + // There is no consent + return { + id: undefined, + reason: 'NO_CLIENT_CONSENT', + }; + } + } - if (!refreshNeeded) { + if (!hasExpiredPanoId) { return { id: localCache.data, }; @@ -203,6 +236,18 @@ export const lotamePanoramaIdSubmodule = { const storedUserId = getProfileId(); + // Add CCPA Consent data handling + const usp = uspDataHandler.getConsentData(); + + let usPrivacy; + if (typeof usp !== 'undefined' && !isEmpty(usp) && !isEmptyStr(usp)) { + usPrivacy = usp; + } + if (!usPrivacy) { + // fallback to 1st party cookie + usPrivacy = getFromStorage('us_privacy'); + } + const resolveIdFunction = function (callback) { let queryParams = {}; if (storedUserId) { @@ -226,6 +271,17 @@ export const lotamePanoramaIdSubmodule = { if (consentString) { queryParams.gdpr_consent = consentString; } + + // Add usPrivacy to the url + if (usPrivacy) { + queryParams.us_privacy = usPrivacy; + } + + // Add clientId to the url + if (hasCustomClientId) { + queryParams.c = clientId; + } + const url = buildUrl({ protocol: 'https', host: `id.crwdcntrl.net`, @@ -239,15 +295,31 @@ export const lotamePanoramaIdSubmodule = { if (response) { try { let responseObj = JSON.parse(response); - const shouldUpdateProfileId = !( + const hasNoConsentErrors = !( isArray(responseObj.errors) && responseObj.errors.indexOf(MISSING_CORE_CONSENT) !== -1 ); + if (hasCustomClientId) { + if (hasNoConsentErrors) { + clearLotameCache(`${KEY_EXPIRY}_${clientId}`); + } else if (isStr(responseObj.no_consent) && responseObj.no_consent === 'CLIENT') { + saveLotameCache( + `${KEY_EXPIRY}_${clientId}`, + responseObj.expiry_ts, + responseObj.expiry_ts + ); + + // End Processing + callback(); + return; + } + } + saveLotameCache(KEY_EXPIRY, responseObj.expiry_ts, responseObj.expiry_ts); if (isStr(responseObj.profile_id)) { - if (shouldUpdateProfileId) { + if (hasNoConsentErrors) { setProfileId(responseObj.profile_id); } @@ -262,7 +334,7 @@ export const lotamePanoramaIdSubmodule = { clearLotameCache(KEY_ID); } } else { - if (shouldUpdateProfileId) { + if (hasNoConsentErrors) { clearLotameCache(KEY_PROFILE); } clearLotameCache(KEY_ID); diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js new file mode 100755 index 00000000000..897dc3c8825 --- /dev/null +++ b/modules/luponmediaBidAdapter.js @@ -0,0 +1,570 @@ +import {isArray, logMessage, deepAccess, logWarn, parseSizesInput, deepSetValue, generateUUID, isEmpty, logError, _each, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER} from '../src/mediaTypes.js'; +import { ajax } from '../src/ajax.js'; + +const BIDDER_CODE = 'luponmedia'; +const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; + +const DIGITRUST_PROP_NAMES = { + PREBID_SERVER: { + id: 'id', + keyv: 'keyv' + } +}; + +var sizeMap = { + 1: '468x60', + 2: '728x90', + 5: '120x90', + 7: '125x125', + 8: '120x600', + 9: '160x600', + 10: '300x600', + 13: '200x200', + 14: '250x250', + 15: '300x250', + 16: '336x280', + 17: '240x400', + 19: '300x100', + 31: '980x120', + 32: '250x360', + 33: '180x500', + 35: '980x150', + 37: '468x400', + 38: '930x180', + 39: '750x100', + 40: '750x200', + 41: '750x300', + 42: '2x4', + 43: '320x50', + 44: '300x50', + 48: '300x300', + 53: '1024x768', + 54: '300x1050', + 55: '970x90', + 57: '970x250', + 58: '1000x90', + 59: '320x80', + 60: '320x150', + 61: '1000x1000', + 64: '580x500', + 65: '640x480', + 66: '930x600', + 67: '320x480', + 68: '1800x1000', + 72: '320x320', + 73: '320x160', + 78: '980x240', + 79: '980x300', + 80: '980x400', + 83: '480x300', + 85: '300x120', + 90: '548x150', + 94: '970x310', + 95: '970x100', + 96: '970x210', + 101: '480x320', + 102: '768x1024', + 103: '480x280', + 105: '250x800', + 108: '320x240', + 113: '1000x300', + 117: '320x100', + 125: '800x250', + 126: '200x600', + 144: '980x600', + 145: '980x150', + 152: '1000x250', + 156: '640x320', + 159: '320x250', + 179: '250x600', + 195: '600x300', + 198: '640x360', + 199: '640x200', + 213: '1030x590', + 214: '980x360', + 221: '1x1', + 229: '320x180', + 230: '2000x1400', + 232: '580x400', + 234: '6x6', + 251: '2x2', + 256: '480x820', + 257: '400x600', + 258: '500x200', + 259: '998x200', + 264: '970x1000', + 265: '1920x1080', + 274: '1800x200', + 278: '320x500', + 282: '320x400', + 288: '640x380', + 548: '500x1000', + 550: '980x480', + 552: '300x200', + 558: '640x640' +}; + +_each(sizeMap, (item, key) => sizeMap[item] = key); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.siteId && bid.params.keyId); // TODO: check for siteId and keyId + }, + buildRequests: function (bidRequests, bidderRequest) { + const bRequest = { + method: 'POST', + url: ENDPOINT_URL, + data: null, + options: {}, + bidderRequest + }; + + let currentImps = []; + + for (let i = 0, len = bidRequests.length; i < len; i++) { + let newReq = newOrtbBidRequest(bidRequests[i], bidderRequest, currentImps); + currentImps = newReq.imp; + bRequest.data = JSON.stringify(newReq); + } + + return bRequest; + }, + interpretResponse: (response, request) => { + const bidResponses = []; + var respCur = 'USD'; + let parsedRequest = JSON.parse(request.data); + let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; + try { + if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { + // Supporting multiple bid responses for same adSize + respCur = response.body.cur || respCur; + response.body.seatbid.forEach(seatbidder => { + seatbidder.bid && + isArray(seatbidder.bid) && + seatbidder.bid.forEach(bid => { + let newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + dealId: bid.dealid, + currency: respCur, + netRevenue: false, + ttl: 300, + referrer: parsedReferrer, + ad: bid.adm + }; + + bidResponses.push(newBid); + }); + }); + } + } catch (error) { + logError(error); + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + let allUserSyncs = []; + if (!hasSynced && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + responses.forEach(csResp => { + if (csResp.body && csResp.body.ext && csResp.body.ext.usersyncs) { + try { + let response = csResp.body.ext.usersyncs + let bidders = response.bidder_status; + for (let synci in bidders) { + let thisSync = bidders[synci]; + if (thisSync.no_cookie) { + let url = thisSync.usersync.url; + let type = thisSync.usersync.type; + + if (!url) { + logError(`No sync url for bidder luponmedia.`); + } else if ((type === 'image' || type === 'redirect') && syncOptions.pixelEnabled) { + logMessage(`Invoking image pixel user sync for luponmedia`); + allUserSyncs.push({type: 'image', url: url}); + } else if (type == 'iframe' && syncOptions.iframeEnabled) { + logMessage(`Invoking iframe user sync for luponmedia`); + allUserSyncs.push({type: 'iframe', url: url}); + } else { + logError(`User sync type "${type}" not supported for luponmedia`); + } + } + } + } catch (e) { + logError(e); + } + } + }); + } else { + logWarn('Luponmedia: Please enable iframe/pixel based user sync.'); + } + + hasSynced = true; + return allUserSyncs; + }, + onBidWon: bid => { + const bidString = JSON.stringify(bid); + spec.sendWinningsToServer(bidString); + }, + sendWinningsToServer: data => { + let mutation = `mutation {createWin(input: {win: {eventData: "${window.btoa(data)}"}}) {win {createTime } } }`; + let dataToSend = JSON.stringify({ query: mutation }); + + ajax('https://analytics.adxpremium.services/graphql', null, dataToSend, { + contentType: 'application/json', + method: 'POST' + }); + } +}; + +export function hasValidSupplyChainParams(schain) { + let isValid = false; + const requiredFields = ['asi', 'sid', 'hp']; + if (!schain.nodes) return isValid; + isValid = schain.nodes.reduce((status, node) => { + if (!status) return status; + return requiredFields.every(field => node[field]); + }, true); + if (!isValid) logError('LuponMedia: required schain params missing'); + return isValid; +} + +var hasSynced = false; + +export function resetUserSync() { + hasSynced = false; +} + +export function masSizeOrdering(sizes) { + const MAS_SIZE_PRIORITY = [15, 2, 9]; + + return sizes.sort((first, second) => { + // sort by MAS_SIZE_PRIORITY priority order + const firstPriority = MAS_SIZE_PRIORITY.indexOf(first); + const secondPriority = MAS_SIZE_PRIORITY.indexOf(second); + + if (firstPriority > -1 || secondPriority > -1) { + if (firstPriority === -1) { + return 1; + } + if (secondPriority === -1) { + return -1; + } + return firstPriority - secondPriority; + } + + // and finally ascending order + return first - second; + }); +} + +function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { + bidRequest.startTime = new Date().getTime(); + + const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); + + let bannerSizes = []; + + if (bannerParams && bannerParams.sizes) { + const sizes = parseSizesInput(bannerParams.sizes); + + // get banner sizes in form [{ w: , h: }, ...] + const format = sizes.map(size => { + const [ width, height ] = size.split('x'); + const w = parseInt(width, 10); + const h = parseInt(height, 10); + return { w, h }; + }); + + bannerSizes = format; + } + + const data = { + id: bidRequest.transactionId, + test: config.getConfig('debug') ? 1 : 0, + source: { + tid: bidRequest.transactionId + }, + tmax: config.getConfig('timeout') || 1500, + imp: currentImps.concat([{ + id: bidRequest.bidId, + secure: 1, + ext: { + [bidRequest.bidder]: bidRequest.params + }, + banner: { + format: bannerSizes + } + }]), + ext: { + prebid: { + targeting: { + includewinners: true, + // includebidderkeys always false for openrtb + includebidderkeys: false + } + } + }, + user: { + } + } + + let bidFloor; + if (isFn(bidRequest.getFloor) && !config.getConfig('disableFloors')) { + let floorInfo; + try { + floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'video', + size: parseSizes(bidRequest, 'video') + }); + } catch (e) { + logError('LuponMedia: getFloor threw an error: ', e); + } + bidFloor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined; + } else { + bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); + } + if (!isNaN(bidFloor)) { + data.imp[0].bidfloor = bidFloor; + } + + appendSiteAppDevice(data, bidRequest, bidderRequest); + + const digiTrust = _getDigiTrustQueryParams(bidRequest, 'PREBID_SERVER'); + if (digiTrust) { + deepSetValue(data, 'user.ext.digitrust', digiTrust); + } + + if (bidderRequest.gdprConsent) { + // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module + let gdprApplies; + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + + deepSetValue(data, 'regs.ext.gdpr', gdprApplies); + deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent) { + deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // Set user uuid + deepSetValue(data, 'user.id', generateUUID()); + + // set crumbs + if (bidRequest.crumbs && bidRequest.crumbs.pubcid) { + deepSetValue(data, 'user.buyeruid', bidRequest.crumbs.pubcid); + } else { + deepSetValue(data, 'user.buyeruid', generateUUID()); + } + + if (bidRequest.userId && typeof bidRequest.userId === 'object' && + (bidRequest.userId.tdid || bidRequest.userId.pubcid || bidRequest.userId.lipb || bidRequest.userId.idl_env)) { + deepSetValue(data, 'user.ext.eids', []); + + if (bidRequest.userId.tdid) { + data.user.ext.eids.push({ + source: 'adserver.org', + uids: [{ + id: bidRequest.userId.tdid, + ext: { + rtiPartner: 'TDID' + } + }] + }); + } + + if (bidRequest.userId.pubcid) { + data.user.ext.eids.push({ + source: 'pubcommon', + uids: [{ + id: bidRequest.userId.pubcid, + }] + }); + } + + // support liveintent ID + if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { + data.user.ext.eids.push({ + source: 'liveintent.com', + uids: [{ + id: bidRequest.userId.lipb.lipbid + }] + }); + + data.user.ext.tpid = { + source: 'liveintent.com', + uid: bidRequest.userId.lipb.lipbid + }; + + if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) { + deepSetValue(data, 'rp.target.LIseg', bidRequest.userId.lipb.segments); + } + } + + // support identityLink (aka LiveRamp) + if (bidRequest.userId.idl_env) { + data.user.ext.eids.push({ + source: 'liveramp.com', + uids: [{ + id: bidRequest.userId.idl_env + }] + }); + } + } + + if (config.getConfig('coppa') === true) { + deepSetValue(data, 'regs.coppa', 1); + } + + if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { + deepSetValue(data, 'source.ext.schain', bidRequest.schain); + } + + const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); + const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); + + if (!isEmpty(siteData) || !isEmpty(userData)) { + const bidderData = { + bidders: [ bidderRequest.bidderCode ], + config: { + fpd: {} + } + }; + + if (!isEmpty(siteData)) { + bidderData.config.fpd.site = siteData; + } + + if (!isEmpty(userData)) { + bidderData.config.fpd.user = userData; + } + + deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); + } + + const pbAdSlot = deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + if (typeof pbAdSlot === 'string' && pbAdSlot) { + deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); + } + + return data; +} + +function _getDigiTrustQueryParams(bidRequest = {}, endpointName) { + if (!endpointName || !DIGITRUST_PROP_NAMES[endpointName]) { + return null; + } + const propNames = DIGITRUST_PROP_NAMES[endpointName]; + + function getDigiTrustId() { + const bidRequestDigitrust = deepAccess(bidRequest, 'userId.digitrustid.data'); + if (bidRequestDigitrust) { + return bidRequestDigitrust; + } + + let digiTrustUser = (window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'}))); + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; + } + + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return null; + } + + const digiTrustQueryParams = { + [propNames.id]: digiTrustId.id, + [propNames.keyv]: digiTrustId.keyv + }; + if (propNames.pref) { + digiTrustQueryParams[propNames.pref] = 0; + } + return digiTrustQueryParams; +} + +function _getPageUrl(bidRequest, bidderRequest) { + let pageUrl = config.getConfig('pageUrl'); + if (bidRequest.params.referrer) { + pageUrl = bidRequest.params.referrer; + } else if (!pageUrl) { + pageUrl = bidderRequest.refererInfo.referer; + } + return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; +} + +function appendSiteAppDevice(data, bidRequest, bidderRequest) { + if (!data) return; + + // ORTB specifies app OR site + if (typeof config.getConfig('app') === 'object') { + data.app = config.getConfig('app'); + } else { + data.site = { + page: _getPageUrl(bidRequest, bidderRequest) + } + } + if (typeof config.getConfig('device') === 'object') { + data.device = config.getConfig('device'); + } +} + +/** + * @param sizes + * @returns {*} + */ +function mapSizes(sizes) { + return parseSizesInput(sizes) + // map sizes while excluding non-matches + .reduce((result, size) => { + let mappedSize = parseInt(sizeMap[size], 10); + if (mappedSize) { + result.push(mappedSize); + } + return result; + }, []); +} + +function parseSizes(bid, mediaType) { + let params = bid.params; + if (mediaType === 'video') { + let size = []; + if (params.video && params.video.playerWidth && params.video.playerHeight) { + size = [ + params.video.playerWidth, + params.video.playerHeight + ]; + } else if (Array.isArray(deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { + size = bid.mediaTypes.video.playerSize[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { + size = bid.sizes[0]; + } + return size; + } + + // Deprecated: temp legacy support + let sizes = []; + if (Array.isArray(params.sizes)) { + sizes = params.sizes; + } else if (typeof deepAccess(bid, 'mediaTypes.banner.sizes') !== 'undefined') { + sizes = mapSizes(bid.mediaTypes.banner.sizes); + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizes = mapSizes(bid.sizes) + } else { + logWarn('LuponMedia: no sizes are setup or found'); + } + + return masSizeOrdering(sizes); +} + +registerBidder(spec); diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index e442b01a115..1043c7cc3e6 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -7,7 +7,6 @@ const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' const BIDDER_URL_TEST = 'https://bidder-test.mediasquare.fr/' const BIDDER_ENDPOINT_AUCTION = 'msq_prebid'; -const BIDDER_ENDPOINT_SYNC = 'cookie_sync'; const BIDDER_ENDPOINT_WINNING = 'winning'; export const spec = { @@ -132,18 +131,11 @@ export const spec = { * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - let params = ''; - let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; 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; } else { - if (gdprConsent && typeof gdprConsent.consentString === 'string') { params += typeof gdprConsent.gdprApplies === 'boolean' ? `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}` : `&gdpr_consent=${gdprConsent.consentString}`; } - if (uspConsent && typeof uspConsent === 'string') { params += '&uspConsent=' + uspConsent } - return { - type: 'iframe', - url: endpoint + BIDDER_ENDPOINT_SYNC + '?type=iframe' + params - }; + return []; } }, diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js index 5339b653596..352c2d074e8 100644 --- a/modules/merkleIdSystem.js +++ b/modules/merkleIdSystem.js @@ -149,6 +149,11 @@ export const merkleIdSubmodule = { logInfo('User ID - merkleId stored id ' + storedId); const configParams = (config && config.params) || {}; + if (typeof configParams.endpoint !== 'string') { + logWarn('User ID - merkleId submodule endpoint string is not defined'); + configParams.endpoint = ID_URL + } + if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) { logError('User ID - merkleId submodule does not currently handle consent strings'); return; diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js new file mode 100644 index 00000000000..30749e977a8 --- /dev/null +++ b/modules/missenaBidAdapter.js @@ -0,0 +1,89 @@ +import { formatQS, logInfo } from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'missena'; +const ENDPOINT_URL = 'https://bid.missena.io/'; + +export const spec = { + aliases: [BIDDER_CODE], + code: BIDDER_CODE, + gvlid: 687, + 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 typeof bid == 'object' && !!bid.params.apiKey; + }, + + /** + * 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) { + return validBidRequests.map((bidRequest) => { + const payload = { + request_id: bidRequest.bidId, + timeout: bidderRequest.timeout, + }; + + if (bidderRequest && bidderRequest.refererInfo) { + payload.referer = bidderRequest.refererInfo.referer; + payload.referer_canonical = bidderRequest.refererInfo.canonicalUrl; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.consent_string = bidderRequest.gdprConsent.consentString; + payload.consent_required = bidderRequest.gdprConsent.gdprApplies; + } + + return { + method: 'POST', + url: ENDPOINT_URL + '?' + formatQS({ t: bidRequest.params.apiKey }), + data: JSON.stringify(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, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + + if (response && !response.timeout && !!response.ad) { + bidResponses.push(response); + } + + return bidResponses; + }, + + /** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + onTimeout: function onTimeout(timeoutData) { + logInfo('Missena - Timeout from adapter', timeoutData); + }, + + /** + * 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) { + logInfo('Missena - Bid won', bid); + }, +}; + +registerBidder(spec); diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index 06586b80102..c9e6a1f659f 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -17,6 +17,16 @@ const adUnitsRequested = {} // Prebid adapter referrence doc: https://docs.prebid.org/dev-docs/bidder-adaptor.html +// Validity checks for optionsl paramters +const validParameter = { + url: (value) => typeof value === 'string', + placementId: (value) => { + const isString = typeof value === 'string' + const isNumber = typeof value === 'number' + return isString || isNumber + }, +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -30,7 +40,23 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return true + // We don't need any specific parameters to make a bid request + // If not parameters are supplied just verify it's the correct bidder code + if (!bid.params) return bid.bidder === BIDDER_CODE + + // Check if any supplied parameters are invalid + const hasInvalidParameters = Object.keys(bid.params).some(key => { + const value = bid.params[key] + const validityCheck = validParameter[key] + + // We don't have a test for this so it's not a paramter we care about + if (!validityCheck) return false + + // Return if the check is not passed + return !validityCheck(value) + }) + + return !hasInvalidParameters }, /** diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index afc409c19f6..4791740bf2f 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -4,6 +4,7 @@ import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; +const SYNC_ENDPOINT = 'https://statics.nextmillmedia.com/load-cookie.html?v=4'; const TIME_TO_LIVE = 360; export const spec = { @@ -18,8 +19,10 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { const requests = []; + window.nmmRefreshCounts = window.nmmRefreshCounts || {}; _each(validBidRequests, function(bid) { + window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; const postBody = { 'id': bid.auctionId, 'ext': { @@ -27,6 +30,9 @@ export const spec = { 'storedrequest': { 'id': getBidIdParameter('placement_id', bid.params) } + }, + 'nextMillennium': { + 'refresh_count': window.nmmRefreshCounts[bid.adUnitCode]++, } } } @@ -40,12 +46,14 @@ export const spec = { if (uspConsent) { postBody.regs.ext.us_privacy = uspConsent; } - if (typeof gdprConsent.gdprApplies !== 'undefined') { - postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; - } - if (typeof gdprConsent.consentString !== 'undefined') { - postBody.user = { - ext: { consent: gdprConsent.consentString } + 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 } + } } } } @@ -89,7 +97,31 @@ export const spec = { }); return bidResponses; - } + }, + + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + if (!syncOptions.iframeEnabled) { + return + } + + let syncurl = gdprConsent && gdprConsent.gdprApplies ? `${SYNC_ENDPOINT}&gdpr=1&gdpr_consent=${gdprConsent.consentString}` : SYNC_ENDPOINT + + let bidders = [] + if (responses) { + _each(responses, (response) => { + _each(Object.keys(response.body.ext.responsetimemillis), b => bidders.push(b)) + }) + } + + if (bidders.length) { + syncurl += `&bidders=${bidders.join(',')}` + } + + return [{ + type: 'iframe', + url: syncurl + }]; + }, }; registerBidder(spec); diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 40843d58d02..c21eaffa48c 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -10,6 +10,7 @@ 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.7'; function isBidRequestValid(bid) { const adUnitSizes = getAdUnitSizes(bid); @@ -55,7 +56,11 @@ function buildRequests(validBidRequests, bidderRequest) { consent: '' } }, - imp: [] + imp: [], + ext: { + adapterversion: ADAPTER_VERSION, + prebidversion: '$prebid.version$' + } }; if (bidderRequest.hasOwnProperty('gdprConsent') && @@ -83,7 +88,8 @@ function buildRequests(validBidRequests, bidderRequest) { bidfloor: getFloor(bidRequest), banner: { format: sizes - } + }, + ext: bidRequest.params }); } }); @@ -122,7 +128,9 @@ function interpretResponse(openRtbBidResponse) { meta: { advertiserDomains: bid.adomain }, - nurl: bid.nurl + nurl: bid.nurl, + adapterVersion: ADAPTER_VERSION, + prebidVersion: '$prebid.version$' }; bidResponse.ad = bid.adm; diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index c8899070e5e..5642dce9018 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -347,11 +347,13 @@ function getSizes(sizes) { function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { let syncs = []; let params = ''; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - params += '&gdpr_consent=' + gdprConsent.consentString; + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { params += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); } + if (typeof gdprConsent.consentString === 'string') { + params += '&gdpr_consent=' + gdprConsent.consentString; + } } if (uspConsent && typeof uspConsent === 'string') { params += '&us_privacy=' + uspConsent; diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index eb165e886e8..60b441e2c10 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -42,7 +42,12 @@ export const USER_ID_CODE_TO_QUERY_ARG = { novatiq: 'novatiqid', // Novatiq ID mwOpenLinkId: 'mwopenlinkid', // MediaWallah OpenLink ID dapId: 'dapid', // Akamai DAP ID - amxId: 'amxid' // AMX RTB ID + amxId: 'amxid', // AMX RTB ID + kpuid: 'kpuid', // Kinesso ID + publinkId: 'publinkid', // Publisher Link + naveggId: 'naveggid', // Navegg ID + imuid: 'imuid', // IM-UID by Intimate Merger + adtelligentId: 'adtelligentid' // Adtelligent ID }; export const spec = { diff --git a/modules/optimeraRtdProvider.js b/modules/optimeraRtdProvider.js index b7ce3c6c6d9..024a558baca 100644 --- a/modules/optimeraRtdProvider.js +++ b/modules/optimeraRtdProvider.js @@ -173,7 +173,9 @@ export function setScoresURL() { } /** - * Set the scores for the divice if given. + * Set the scores for the device if given. + * Add any any insights to the winddow.optimeraInsights object. + * * @param {*} result * @returns {string} JSON string of Optimera Scores. */ @@ -184,6 +186,11 @@ export function setScores(result) { if (device !== 'default' && scores.device[device]) { scores = scores.device[device]; } + logInfo(scores); + if (scores.insights) { + window.optimeraInsights = window.optimeraInsights || {}; + window.optimeraInsights.data = scores.insights; + } } catch (e) { logError('Optimera score file could not be parsed.'); } diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 5b46a2cb80b..3b5147907eb 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -4,16 +4,16 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {getPriceBucketString} from '../src/cpmBucketManager.js'; import { Renderer } from '../src/Renderer.js'; + const BIDDER_CODE = 'ozone'; -// *** PROD *** const ORIGIN = 'https://elb.the-ozone-project.com' // applies only to auction & cookie const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; const ORIGIN_DEV = 'https://test.ozpr.net'; -const OZONEVERSION = '2.6.0'; +const OZONEVERSION = '2.7.0'; export const spec = { gvlid: 524, aliases: [{code: 'lmc', gvlid: 524}, {code: 'newspassid', gvlid: 524}], @@ -52,6 +52,7 @@ export const spec = { this.propertyBag.whitelabel.auctionUrl = bidderConfig.endpointOverride.origin + AUCTIONURI; this.propertyBag.whitelabel.cookieSyncUrl = bidderConfig.endpointOverride.origin + OZONECOOKIESYNC; } + if (arr.hasOwnProperty('renderer')) { if (arr.renderer.match('%3A%2F%2F')) { this.propertyBag.whitelabel.rendererUrl = decodeURIComponent(arr['renderer']); @@ -99,9 +100,9 @@ export const spec = { this.loadWhitelabelData(bid); logInfo('isBidRequestValid : ', config.getConfig(), bid); let adUnitCode = bid.adUnitCode; // adunit[n].code - + let err1 = 'VALIDATION FAILED : missing {param} : siteId, placementId and publisherId are REQUIRED' if (!(bid.params.hasOwnProperty('placementId'))) { - logError('VALIDATION FAILED : missing placementId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + logError(err1.replace('{param}', 'placementId'), adUnitCode); return false; } if (!this.isValidPlacementId(bid.params.placementId)) { @@ -109,7 +110,7 @@ export const spec = { return false; } if (!(bid.params.hasOwnProperty('publisherId'))) { - logError('VALIDATION FAILED : missing publisherId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + logError(err1.replace('{param}', 'publisherId'), adUnitCode); return false; } if (!(bid.params.publisherId).toString().match(/^[a-zA-Z0-9\-]{12}$/)) { @@ -117,7 +118,7 @@ export const spec = { return false; } if (!(bid.params.hasOwnProperty('siteId'))) { - logError('VALIDATION FAILED : missing siteId : siteId, placementId and publisherId are REQUIRED', adUnitCode); + logError(err1.replace('{param}', 'siteId'), adUnitCode); return false; } if (!(bid.params.siteId).toString().match(/^[0-9]{10}$/)) { @@ -173,7 +174,6 @@ export const spec = { let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${OZONEVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); - // First check - is there any config to block this request? if (this.blockTheRequest()) { return []; } @@ -191,7 +191,6 @@ 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'] - // First party data module : look for ortb2 in setconfig & set the User object. NOTE THAT this should happen before we set the consentString let fpd = config.getConfig('ortb2'); if (fpd && deepAccess(fpd, 'user')) { logInfo('added FPD user object'); @@ -203,14 +202,13 @@ export const spec = { const isTestMode = getParams[wlOztestmodeKey] || null; // this can be any string, it's used for testing ads ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string - // build the array of params to attach to `imp` + let schain = null; let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; let placementId = placementIdOverrideFromGetParam || this.getPlacementId(ozoneBidRequest); // prefer to use a valid override param, else the bidRequest placement Id obj.id = ozoneBidRequest.bidId; // this causes an error if we change it to something else, even if you update the bidRequest object: "WARNING: Bidder ozone made bid for unknown request ID: mb7953.859498327448. Ignoring." obj.tagid = placementId; obj.secure = window.location.protocol === 'https:' ? 1 : 0; - // is there a banner (or nothing declared, so banner is the default)? let arrBannerSizes = []; if (!ozoneBidRequest.hasOwnProperty('mediaTypes')) { if (ozoneBidRequest.hasOwnProperty('sizes')) { @@ -226,13 +224,11 @@ export const spec = { } if (ozoneBidRequest.mediaTypes.hasOwnProperty(VIDEO)) { logInfo('openrtb 2.5 compliant video'); - // examine all the video attributes in the config, and either put them into obj.video if allowed by IAB2.5 or else in to obj.video.ext if (typeof ozoneBidRequest.mediaTypes[VIDEO] == 'object') { let childConfig = deepAccess(ozoneBidRequest, 'params.video', {}); obj.video = this.unpackVideoConfigIntoIABformat(ozoneBidRequest.mediaTypes[VIDEO], childConfig); obj.video = this.addVideoDefaults(obj.video, ozoneBidRequest.mediaTypes[VIDEO], childConfig); } - // we need to duplicate some of the video values let wh = getWidthAndHeightFromVideoObject(obj.video); logInfo('setting video object from the mediaTypes.video element: ' + obj.id + ':', obj.video, 'wh=', wh); if (wh && typeof wh === 'object') { @@ -249,12 +245,10 @@ export const spec = { logWarn('cannot set w, h & format values for video; the config is not right'); } } - // Native integration is not complete yet if (ozoneBidRequest.mediaTypes.hasOwnProperty(NATIVE)) { obj.native = ozoneBidRequest.mediaTypes[NATIVE]; logInfo('setting native object from the mediaTypes.native element: ' + obj.id + ':', obj.native); } - // is the publisher specifying floors, and is the floors module enabled? if (ozoneBidRequest.hasOwnProperty('getFloor')) { logInfo('This bidRequest object has property: getFloor'); obj.floor = this.getFloorObjectForAuction(ozoneBidRequest); @@ -264,7 +258,6 @@ export const spec = { } } if (arrBannerSizes.length > 0) { - // build the banner request using banner sizes we found in either possible location: obj.banner = { topframe: 1, w: arrBannerSizes[0][0] || 0, @@ -274,11 +267,8 @@ export const spec = { }) }; } - // these 3 MUST exist - we check them in the validation method obj.placementId = placementId; - // build the imp['ext'] object - NOTE - Dont obliterate anything that' already in obj.ext deepSetValue(obj, 'ext.prebid', {'storedrequest': {'id': placementId}}); - // obj.ext = {'prebid': {'storedrequest': {'id': placementId}}}; obj.ext[whitelabelBidder] = {}; obj.ext[whitelabelBidder].adUnitCode = ozoneBidRequest.adUnitCode; // eg. 'mpu' obj.ext[whitelabelBidder].transactionId = ozoneBidRequest.transactionId; // this is the transactionId PER adUnit, common across bidders for this unit @@ -298,33 +288,30 @@ export const spec = { } } if (fpd && deepAccess(fpd, 'site')) { - // attach the site fpd into exactly : imp[n].ext.[whitelabel].customData.0.targeting - logInfo('added FPD site object'); + logInfo('added fpd.site'); if (deepAccess(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', false)) { obj.ext[whitelabelBidder].customData[0].targeting = Object.assign(obj.ext[whitelabelBidder].customData[0].targeting, fpd.site); - // let keys = getKeys(fpd.site); - // for (let i = 0; i < keys.length; i++) { - // obj.ext[whitelabelBidder].customData[0].targeting[keys[i]] = fpd.site[keys[i]]; - // } } else { deepSetValue(obj, 'ext.' + whitelabelBidder + '.customData.0.targeting', fpd.site); } } + if (!schain && deepAccess(ozoneBidRequest, 'schain')) { + schain = ozoneBidRequest.schain; + } return obj; }); - // in v 2.0.0 we moved these outside of the individual ad slots let extObj = {}; extObj[whitelabelBidder] = {}; extObj[whitelabelBidder][whitelabelPrefix + '_pb_v'] = OZONEVERSION; extObj[whitelabelBidder][whitelabelPrefix + '_rw'] = placementIdOverrideFromGetParam ? 1 : 0; if (validBidRequests.length > 0) { let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info - // let userIds = this.findAllUserIds(validBidRequests[0]); if (userIds.hasOwnProperty('pubcid')) { extObj[whitelabelBidder].pubcid = userIds.pubcid; } } + extObj[whitelabelBidder].pv = this.getPageId(); // attach the page ID that will be common to all auciton calls for this page if refresh() is called let ozOmpFloorDollars = this.getWhitelabelConfigItem('ozone.oz_omp_floor'); // valid only if a dollar value (typeof == 'number') logInfo(`${whitelabelPrefix}_omp_floor dollar value = `, ozOmpFloorDollars); @@ -340,14 +327,12 @@ export const spec = { logInfo('setting aliases object'); extObj.prebid = {aliases: {'ozone': whitelabelBidder}}; } - // 20210413 - adding a set of GET params to pass to auction if (getParams.hasOwnProperty('ozf')) { extObj[whitelabelBidder]['ozf'] = getParams.ozf == 'true' || getParams.ozf == 1 ? 1 : 0; } if (getParams.hasOwnProperty('ozpf')) { extObj[whitelabelBidder]['ozpf'] = getParams.ozpf == 'true' || getParams.ozpf == 1 ? 1 : 0; } if (getParams.hasOwnProperty('ozrp') && getParams.ozrp.match(/^[0-3]$/)) { extObj[whitelabelBidder]['ozrp'] = parseInt(getParams.ozrp); } if (getParams.hasOwnProperty('ozip') && getParams.ozip.match(/^\d+$/)) { extObj[whitelabelBidder]['ozip'] = parseInt(getParams.ozip); } if (this.propertyBag.endpointOverride != null) { extObj[whitelabelBidder]['origin'] = this.propertyBag.endpointOverride; } - // extObj.ortb2 = config.getConfig('ortb2'); // original test location var userExtEids = this.generateEids(validBidRequests); // generate the UserIDs in the correct format for UserId module ozoneRequest.site = { @@ -357,7 +342,6 @@ export const spec = { }; ozoneRequest.test = (getParams.hasOwnProperty('pbjs_debug') && getParams['pbjs_debug'] === 'true') ? 1 : 0; - // this should come as late as possible so it overrides any user.ext.consent value if (bidderRequest && bidderRequest.gdprConsent) { logInfo('ADDING GDPR info'); let apiVersion = deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1); @@ -376,21 +360,22 @@ export const spec = { } else { logInfo('WILL NOT ADD CCPA info; no bidderRequest.uspConsent.'); } + if (schain) { // we set this while iterating over the bids + logInfo('schain found'); + deepSetValue(ozoneRequest, 'source.ext.schain', schain); + } - // this is for 2.2.1 - // coppa compliance if (config.getConfig('coppa') === true) { deepSetValue(ozoneRequest, 'regs.coppa', 1); } - // return the single request object OR the array: if (singleRequest) { logInfo('buildRequests starting to generate response for a single request'); ozoneRequest.id = bidderRequest.auctionId; // Unique ID of the bid request, provided by the exchange. ozoneRequest.auctionId = bidderRequest.auctionId; // not sure if this should be here? ozoneRequest.imp = tosendtags; ozoneRequest.ext = extObj; - ozoneRequest.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(ozoneRequest, '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(ozoneRequest, 'user.ext.eids', userExtEids); var ret = { method: 'POST', @@ -403,7 +388,6 @@ export const spec = { logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); return ret; } - // not single request - pull apart the tosendtags array & return an array of objects each containing one element in the imp array. let arrRet = tosendtags.map(imp => { logInfo('buildRequests starting to generate non-single response, working on imp : ', imp); let ozoneRequestSingle = Object.assign({}, ozoneRequest); @@ -412,7 +396,7 @@ export const spec = { ozoneRequestSingle.auctionId = imp.ext[whitelabelBidder].transactionId; // not sure if this should be here? ozoneRequestSingle.imp = [imp]; ozoneRequestSingle.ext = extObj; - ozoneRequestSingle.source = {'tid': imp.ext[whitelabelBidder].transactionId}; + deepSetValue(ozoneRequestSingle, 'source.tid', imp.ext[whitelabelBidder].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(ozoneRequestSingle, 'user.ext.eids', userExtEids); logInfo('buildRequests RequestSingle (for non-single) = ', ozoneRequestSingle); return { @@ -477,7 +461,6 @@ export const spec = { logInfo(`interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); serverResponse = serverResponse.body || {}; - // note that serverResponse.id value is the auction_id we might want to use for reporting reasons. if (!serverResponse.hasOwnProperty('seatbid')) { return []; } @@ -492,7 +475,6 @@ export const spec = { } logInfo('enhancedAdserverTargeting', enhancedAdserverTargeting); - // 2021-03-05 - comment this out for a build without adding adid to the response 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); @@ -508,7 +490,6 @@ export const spec = { 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 = ozoneAddStandardProperties(sb.bid[j], defaultWidth, defaultHeight); - // prebid 4.0 compliance thisBid.meta = {advertiserDomains: thisBid.adomain || []}; let videoContext = null; let isVideo = false; @@ -527,11 +508,9 @@ export const spec = { let adserverTargeting = {}; if (enhancedAdserverTargeting) { let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); - // add all the winning & non-winning bids for this bidId: logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); - // let bidderName = bidderNameWH.split('_')[0]; adserverTargeting[whitelabelPrefix + '_' + bidderName] = bidderName; adserverTargeting[whitelabelPrefix + '_' + bidderName + '_crid'] = String(allBidsForThisBidid[bidderName].crid); adserverTargeting[whitelabelPrefix + '_' + bidderName + '_adv'] = String(allBidsForThisBidid[bidderName].adomain); @@ -565,7 +544,6 @@ export const spec = { logInfo(`${whitelabelBidder}.enhancedAdserverTargeting is set to false, so no per-bid keys will be sent to adserver.`); } } - // also add in the winning bid, to be sent to dfp let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); adserverTargeting[whitelabelPrefix + '_auc_id'] = String(request.bidderRequest.auctionId); adserverTargeting[whitelabelPrefix + '_winner'] = String(winningSeat); @@ -594,14 +572,14 @@ export const spec = { /** * Use this to get all config values * Now it's getting complicated with whitelabeling, this simplifies the code for getting config values. - * eg. to get ozone.oz_omp_floor you just send '_omp_floor' + * eg. to get whitelabelled version you just sent the ozone default string like ozone.oz_omp_floor * @param ozoneVersion string like 'ozone.oz_omp_floor' * @return {string|object} */ getWhitelabelConfigItem(ozoneVersion) { if (this.propertyBag.whitelabel.bidder == 'ozone') { return config.getConfig(ozoneVersion); } let whitelabelledSearch = ozoneVersion.replace('ozone', this.propertyBag.whitelabel.bidder); - whitelabelledSearch = ozoneVersion.replace('oz_', this.propertyBag.whitelabel.keyPrefix + '_'); + whitelabelledSearch = whitelabelledSearch.replace('oz_', this.propertyBag.whitelabel.keyPrefix + '_'); return config.getConfig(whitelabelledSearch); }, /** @@ -631,8 +609,6 @@ export const spec = { } return ret; }, - // see http://prebid.org/dev-docs/bidder-adaptor.html#registering-user-syncs - // us privacy: https://docs.prebid.org/dev-docs/modules/consentManagementUsp.html getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'gdprConsent', gdprConsent, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); if (!serverResponse || serverResponse.length === 0) { @@ -646,11 +622,6 @@ export const spec = { arrQueryString.push('gdpr=' + (deepAccess(gdprConsent, 'gdprApplies', false) ? '1' : '0')); arrQueryString.push('gdpr_consent=' + deepAccess(gdprConsent, 'consentString', '')); arrQueryString.push('usp_consent=' + (usPrivacy || '')); - // var objKeys = Object.getOwnPropertyNames(this.cookieSyncBag.userIdObject); - // for (let idx in objKeys) { - // let keyname = objKeys[idx]; - // arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); - // } for (let keyname in this.cookieSyncBag.userIdObject) { arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); } @@ -704,10 +675,7 @@ export const spec = { */ findAllUserIds(bidRequest) { var ret = {}; - // @todo - what is Neustar fabrick called & where to look for it? If it's a simple value then it will automatically be ok - // it is not in the table 'Bidder Adapter Implementation' on https://docs.prebid.org/dev-docs/modules/userId.html#prebidjs-adapters let searchKeysSingle = ['pubcid', 'tdid', 'idl_env', 'criteoId', 'lotamePanoramaId', 'fabrickId']; - if (bidRequest.hasOwnProperty('userId')) { for (let arrayId in searchKeysSingle) { let key = searchKeysSingle[arrayId]; @@ -716,7 +684,6 @@ export const spec = { 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.`); - // fallback - get the value of the first key in the object; this is NOT desirable behaviour 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}`); @@ -815,7 +782,6 @@ export const spec = { }); } }, - // Try to use this as the mechanism for reading GET params because it's easy to mock it for tests getGetParametersAsObject() { let items = location.search.substr(1).split('&'); let ret = {}; @@ -831,7 +797,6 @@ export const spec = { * @return {boolean|*[]} true = block the request, else false */ blockTheRequest() { - // if there is an ozone.oz_request = false then quit now. let ozRequest = this.getWhitelabelConfigItem('ozone.oz_request'); if (typeof ozRequest == 'boolean' && !ozRequest) { logWarn(`Will not allow auction : ${this.propertyBag.whitelabel.keyPrefix}one.${this.propertyBag.whitelabel.keyPrefix}_request is set to false`); @@ -882,7 +847,6 @@ export const spec = { ret.ext[key] = objConfig[key]; } } - // handle ext separately, if it exists; we have probably built up an ext object already if (objConfig.hasOwnProperty('ext') && typeof objConfig.ext === 'object') { if (objConfig.hasOwnProperty('ext')) { ret.ext = mergeDeep(ret.ext, objConfig.ext); @@ -906,7 +870,6 @@ export const spec = { * @private */ _addVideoDefaults(objRet, objConfig, addIfMissing) { - // add inferred values & any default values we want. let context = deepAccess(objConfig, 'context'); if (context === 'outstream') { objRet.placement = 3; @@ -936,8 +899,6 @@ export function injectAdIdsIntoAllBidResponses(seatbid) { for (let i = 0; i < seatbid.length; i++) { let sb = seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { - // modify the bidId per-bid, so each bid has a unique adId within this response, and dfp can select one. - // 2020-06 we now need a second level of ID because there might be multiple identical impid's within a seatbid! sb.bid[j]['adId'] = `${sb.bid[j]['impid']}-${i}-${spec.propertyBag.whitelabel.keyPrefix}-${j}`; } } @@ -985,7 +946,6 @@ export function ozoneGetWinnerForRequestBid(requestBidId, serverResponseSeatBid) let thisSeat = serverResponseSeatBid[j].seat; for (let k = 0; k < theseBids.length; k++) { if (theseBids[k].impid === requestBidId) { - // we've found a matching server response bid for this request bid if ((thisBidWinner == null) || (thisBidWinner.price < theseBids[k].price)) { thisBidWinner = theseBids[k]; winningSeat = thisSeat; @@ -1011,7 +971,6 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { 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 - // objBids[`${thisSeat}${theseBids[k].w}x${theseBids[k].h}`] = theseBids[k]; if (objBids[thisSeat]['price'] < theseBids[k].price) { objBids[thisSeat] = theseBids[k]; } @@ -1044,7 +1003,6 @@ export function getRoundedBid(price, mediaType) { config.getConfig('currency.granularityMultiplier') ); logInfo('priceStringsObj', priceStringsObj); - // by default, without any custom granularity set, you get granularity name : 'medium' let granularityNamePriceStringsKeyMapping = { 'medium': 'med', 'custom': 'custom', @@ -1195,7 +1153,6 @@ function newRenderer(adUnitCode, rendererOptions = {}) { } function outstreamRender(bid) { logInfo('outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid =', JSON.parse(JSON.stringify(bid))); - // push to render queue because ozoneVideo may not be loaded yet bid.renderer.push(() => { window.ozoneVideo.outstreamRender(bid); }); diff --git a/modules/pilotxBidAdapter.js b/modules/pilotxBidAdapter.js new file mode 100644 index 00000000000..335c461e3d9 --- /dev/null +++ b/modules/pilotxBidAdapter.js @@ -0,0 +1,147 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +const BIDDER_CODE = 'pilotx'; +const ENDPOINT_URL = '//adn.pilotx.tv/hb' +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + aliases: ['pilotx'], // short code + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + let sizesCheck = !!bid.sizes + let paramSizesCheck = !!bid.params.sizes + var sizeConfirmed = false + if (sizesCheck) { + if (bid.sizes.length < 1) { + return false + } else { + sizeConfirmed = true + } + } + if (paramSizesCheck) { + if (bid.params.sizes.length < 1 && !sizeConfirmed) { + return false + } else { + sizeConfirmed = true + } + } + if (!sizeConfirmed) { + return false + } + return !!(bid.params.placementId); + }, + /** + * 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) { + let payloadItems = {}; + validBidRequests.forEach(bidRequest => { + let sizes = []; + let placementId = this.setPlacementID(bidRequest.params.placementId) + payloadItems[placementId] = {} + if (bidRequest.sizes.length > 0) { + if (Array.isArray(bidRequest.sizes[0])) { + for (let i = 0; i < bidRequest.sizes.length; i++) { + sizes[i] = [(bidRequest.sizes[i])[0], (bidRequest.sizes[i])[1]] + } + } else { + sizes[0] = [bidRequest.sizes[0], bidRequest.sizes[1]] + } + payloadItems[placementId]['sizes'] = sizes + } + if (bidRequest.mediaTypes != null) { + for (let i in bidRequest.mediaTypes) { + payloadItems[placementId][i] = { + ...bidRequest.mediaTypes[i] + } + } + } + let consentTemp = '' + let consentRequiredTemp = false + if (bidderRequest && bidderRequest.gdprConsent) { + consentTemp = bidderRequest.gdprConsent.consentString + // will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true + consentRequiredTemp = (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + } + + payloadItems[placementId]['gdprConsentString'] = consentTemp + payloadItems[placementId]['gdprConsentRequired'] = consentRequiredTemp + payloadItems[placementId]['bidId'] = bidRequest.bidId + }); + const payload = payloadItems; + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_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 = []; + if (serverBody.mediaType == 'banner') { + const bidResponse = { + requestId: serverBody.requestId, + cpm: serverBody.cpm, + width: serverBody.width, + height: serverBody.height, + creativeId: serverBody.creativeId, + currency: serverBody.currency, + netRevenue: false, + ttl: serverBody.ttl, + ad: serverBody.ad, + mediaType: 'banner', + meta: { + mediaType: 'banner', + advertiserDomains: serverBody.advertiserDomains + } + } + bidResponses.push(bidResponse) + } else if (serverBody.mediaType == 'video') { + const bidResponse = { + requestId: serverBody.requestId, + cpm: serverBody.cpm, + width: serverBody.width, + height: serverBody.height, + creativeId: serverBody.creativeId, + currency: serverBody.currency, + netRevenue: false, + ttl: serverBody.ttl, + vastUrl: serverBody.vastUrl, + mediaType: 'video', + meta: { + mediaType: 'video', + advertiserDomains: serverBody.advertiserDomains + } + } + bidResponses.push(bidResponse) + } + + return bidResponses; + }, + + /** + * Formats placement ids for adserver ingestion purposes + * @param {string[]} The placement ID/s in an array + */ + setPlacementID: function (placementId) { + if (Array.isArray(placementId)) { + return placementId.join('#') + } + return placementId + }, +} +registerBidder(spec); diff --git a/modules/pilotxBidAdapter.md b/modules/pilotxBidAdapter.md new file mode 100644 index 00000000000..37489bda4a0 --- /dev/null +++ b/modules/pilotxBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +``` +Module Name: Pilotx Prebid Adapter +Module Type: Bidder Adapter +Maintainer: tony@pilotx.tv +``` + +# Description + +Connects to Pilotx + +Pilotx's bid adapter supports banner and video. + +# Test Parameters +``` +// Banner adUnit +var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + bids: [{ + bidder: 'pilotx', + params: { + placementId: ["1423"] + } + }] + +}]; + +// Video adUnit +var videoAdUnit = { + code: 'video1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + } + }, + bids: [{ + bidder: 'pilotx', + params: { + placementId: '1422', + } + }] +}; +``` \ No newline at end of file diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index e82fdfd02a9..efe2576efd5 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -56,6 +56,7 @@ let eidPermissions; * @property {string} [adapter='prebidServer'] adapter code to use for S2S * @property {boolean} [enabled=false] enables S2S bidding * @property {number} [timeout=1000] timeout for S2S bidders - should be lower than `pbjs.requestBids({timeout})` + * @property {number} [syncTimeout=1000] timeout for cookie sync iframe / image rendering * @property {number} [maxBids=1] * @property {AdapterOptions} [adapterOptions] adds arguments to resulting OpenRTB payload to Prebid Server * @property {Object} [syncUrlModifier] @@ -77,6 +78,7 @@ let eidPermissions; */ const s2sDefaultConfig = { timeout: 1000, + syncTimeout: 1000, maxBids: 1, adapter: 'prebidServer', adapterOptions: {}, @@ -274,11 +276,9 @@ function doAllSyncs(bidders, s2sConfig) { */ function doPreBidderSync(type, url, bidder, done, s2sConfig) { if (s2sConfig.syncUrlModifier && typeof s2sConfig.syncUrlModifier[bidder] === 'function') { - const newSyncUrl = s2sConfig.syncUrlModifier[bidder](type, url, bidder); - doBidderSync(type, newSyncUrl, bidder, done) - } else { - doBidderSync(type, url, bidder, done) + url = s2sConfig.syncUrlModifier[bidder](type, url, bidder); } + doBidderSync(type, url, bidder, done, s2sConfig.syncTimeout) } /** @@ -288,17 +288,18 @@ function doPreBidderSync(type, url, bidder, done, s2sConfig) { * @param {string} url the url to sync * @param {string} bidder name of bidder doing sync for * @param {function} done an exit callback; to signify this pixel has either: finished rendering or something went wrong + * @param {number} timeout: maximum time to wait for rendering in milliseconds */ -function doBidderSync(type, url, bidder, done) { +function doBidderSync(type, url, bidder, done, timeout) { if (!url) { logError(`No sync url for bidder "${bidder}": ${url}`); done(); } else if (type === 'image' || type === 'redirect') { logMessage(`Invoking image pixel user sync for bidder: "${bidder}"`); - triggerPixel(url, done); - } else if (type == 'iframe') { + triggerPixel(url, done, timeout); + } else if (type === 'iframe') { logMessage(`Invoking iframe user sync for bidder: "${bidder}"`); - insertUserSyncIframe(url, done); + insertUserSyncIframe(url, done, timeout); } else { logError(`User sync type "${type}" not supported for bidder: "${bidder}"`); done(); @@ -539,10 +540,13 @@ const OPEN_RTB_PROTOCOL = { } if (Array.isArray(params.aspect_ratios)) { // pass aspect_ratios as ext data I guess? - asset.ext = { - aspectratios: params.aspect_ratios.map( - ratio => `${ratio.ratio_width}:${ratio.ratio_height}` - ) + const aspectRatios = params.aspect_ratios + .filter((ar) => ar.ratio_width && ar.ratio_height) + .map(ratio => `${ratio.ratio_width}:${ratio.ratio_height}`); + if (aspectRatios.length > 0) { + asset.ext = { + aspectratios: aspectRatios + } } } assets.push(newAsset({ @@ -731,7 +735,7 @@ const OPEN_RTB_PROTOCOL = { return; } const request = { - id: s2sBidRequest.tid, + id: firstBidRequest.auctionId, source: {tid: s2sBidRequest.tid}, tmax: s2sConfig.timeout, imp: imps, @@ -751,7 +755,7 @@ const OPEN_RTB_PROTOCOL = { } }; - // Sets pbjs version, can be overwritten below if channel exists in s2sConfig.extPrebid + // 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}}) // set debug flag if in debug mode @@ -761,7 +765,7 @@ const OPEN_RTB_PROTOCOL = { // s2sConfig video.ext.prebid is passed through openrtb to PBS if (s2sConfig.extPrebid && typeof s2sConfig.extPrebid === 'object') { - request.ext.prebid = Object.assign(request.ext.prebid, s2sConfig.extPrebid); + request.ext.prebid = mergeDeep(request.ext.prebid, s2sConfig.extPrebid); } /** diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js index b747c5aca2d..42a98bcdb09 100644 --- a/modules/proxistoreBidAdapter.js +++ b/modules/proxistoreBidAdapter.js @@ -172,8 +172,6 @@ function interpretResponse(serverResponse, bidRequest) { function _assignFloor(bid) { if (!isFn(bid.getFloor)) { - // eslint-disable-next-line no-console - console.log(bid.params.bidFloor); return bid.params.bidFloor ? bid.params.bidFloor : null; } const floor = bid.getFloor({ diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index 89dea545434..28c4fdefd42 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -16,7 +16,7 @@ import { } from '../src/utils.js'; const BIDDER_VERSION = '1.1.0'; -const BASE_URL = 'https://ortb.adpearl.io'; +const BASE_URL = 'https://auction.adpearl.io'; export const spec = { code: 'pubgenius', diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 2477ab4c0a3..f69fb20e5d5 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -200,6 +200,21 @@ function getAdapterNameForAlias(aliasName) { return adapterManager.aliasRegistry[aliasName] || aliasName; } +function getAdDomain(bidResponse) { + if (bidResponse.meta && bidResponse.meta.advertiserDomains) { + let adomain = bidResponse.meta.advertiserDomains[0] + if (adomain) { + try { + let hostname = (new URL(adomain)); + return hostname.hostname.replace('www.', ''); + } catch (e) { + logWarn(LOG_PRE_FIX + 'Adomain URL (Not a proper URL):', adomain); + return adomain.replace('www.', ''); + } + } + } +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { @@ -218,6 +233,7 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { '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), diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 462c3b94509..366a0326054 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -11,6 +11,7 @@ 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 UNDEFINED = undefined; const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; @@ -866,10 +867,10 @@ function _handleEids(payload, validBidRequests) { function _checkMediaType(bid, newBid) { // Create a regex here to check the strings - if (bid.ext && bid.ext['BidType'] != undefined) { - newBid.mediaType = MEDIATYPE[bid.ext.BidType]; + if (bid.ext && bid.ext['bidtype'] != undefined) { + newBid.mediaType = MEDIATYPE[bid.ext.bidtype]; } else { - logInfo(LOG_WARN_PREFIX + 'bid.ext.BidType does not exist, checking alternatively for mediaType') + logInfo(LOG_WARN_PREFIX + 'bid.ext.bidtype does not exist, checking alternatively for mediaType') var adm = bid.adm; var admStr = ''; var videoRegex = new RegExp(/VAST\s+version/); @@ -1005,6 +1006,7 @@ 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 * @@ -1078,7 +1080,7 @@ export const spec = { bid = deepClone(originalBid); bid.params.adSlot = bid.params.adSlot || ''; _parseAdSlot(bid); - if ((bid.mediaTypes && bid.mediaTypes.hasOwnProperty('video')) || bid.params.hasOwnProperty('video')) { + if (bid.params.hasOwnProperty('video')) { // Nothing to do } else { // If we have a native mediaType configured alongside banner, its ok if the banner size is not set in width and height diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 3232d34ccba..0031ff5539b 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -21,6 +21,14 @@ let events = { deviceDetail: {} }; +function getStorage() { + try { + return window.top['sessionStorage']; + } catch (e) { + return null; + } +} + var pubxaiAnalyticsAdapter = Object.assign(adapter( { emptyUrl, @@ -132,6 +140,7 @@ pubxaiAnalyticsAdapter.shouldFireEventRequest = function (samplingRate = 1) { function send(data, status) { if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { let location = getWindowLocation(); + const storage = getStorage(); data.initOptions = initOptions; if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { Object.assign(data.pageDetail, { @@ -142,6 +151,13 @@ function send(data, status) { }); data.initOptions.auctionId = data.auctionInit.auctionId; delete data.auctionInit; + + data.pmcDetail = {} + Object.assign(data.pmcDetail, { + bidDensity: storage ? storage.getItem('pbx:dpbid') : null, + maxBid: storage ? storage.getItem('pbx:mxbid') : null, + auctionId: storage ? storage.getItem('pbx:aucid') : null, + }); } data.deviceDetail = {}; Object.assign(data.deviceDetail, { @@ -150,6 +166,7 @@ function send(data, status) { deviceOS: getOS(), browser: getBrowser() }); + let pubxaiAnalyticsRequestUrl = buildUrl({ protocol: 'https', hostname: (initOptions && initOptions.hostName) || defaultHost, diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index 16e01f80819..128fd72996b 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.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.6'; +const ADAPTER_VERSION = '1.0.7'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; @@ -31,14 +31,14 @@ function isBidRequestValid(bid) { } function buildRequests(validBidRequests, bidderRequest) { - let bidRequests = []; + const bids = []; + let imuid = null; + let bidDomain = null; + let bidder = null; + let count = null; for (let i = 0; i < validBidRequests.length; i++) { const bidRequest = validBidRequests[i]; - const placementId = getBidIdParameter('placementId', bidRequest.params); - const bidDomain = bidRequest.params.domain || BIDDER_DOMAIN; - const bidUrl = `https://${bidDomain}/bid/v1/prebid/${placementId}`; - const uuid = getUuid(); let mediaType = ''; let width = 0; let height = 0; @@ -55,46 +55,63 @@ function buildRequests(validBidRequests, bidderRequest) { mediaType = BANNER; } - let payload = { - version: ADAPTER_VERSION, - timeout_ms: bidderRequest.timeout, - ad_unit_code: bidRequest.adUnitCode, - auction_id: bidRequest.auctionId, - bidder: bidRequest.bidder, - bidder_request_id: bidRequest.bidderRequestId, - bid_requests_count: bidRequest.bidRequestsCount, - bid_id: bidRequest.bidId, - transaction_id: bidRequest.transactionId, - media_type: mediaType, - uuid: uuid, - width: width, - height: height, - pv: '$prebid.version$' - }; + if (!imuid) { + const pickImuid = deepAccess(bidRequest, 'userId.imuid'); + if (pickImuid) { + imuid = pickImuid; + } + } + + if (!bidDomain) { + bidDomain = bidRequest.params.domain; + } + + if (!bidder) { + bidder = bidRequest.bidder + } + + if (!bidder) { + bidder = bidRequest.bidder + } - const imuid = deepAccess(bidRequest, 'userId.imuid'); - if (imuid) { - payload.imuid = imuid; + if (!count) { + count = bidRequest.bidRequestsCount; } - // It may not be encoded, so add it at the end of the payload - payload.ref = bidderRequest.refererInfo.referer; - - bidRequests.push({ - method: 'GET', - url: bidUrl, - data: payload, - options: { - withCredentials: true - }, - bidId: bidRequest.bidId, + bids.push({ + bid_id: bidRequest.bidId, + placement_id: getBidIdParameter('placementId', bidRequest.params), + transaction_id: bidRequest.transactionId, + bidder_request_id: bidRequest.bidderRequestId, + ad_unit_code: bidRequest.adUnitCode, + auction_id: bidRequest.auctionId, player: bidRequest.params.player, - width: payload.width, - height: payload.height, - mediaType: payload.media_type + width: width, + height: height, + media_type: mediaType }); } - return bidRequests; + + const data = JSON.stringify({ + version: ADAPTER_VERSION, + bids: bids, + timeout_ms: bidderRequest.timeout, + bidder: bidder, + bid_requests_count: count, + uuid: getUuid(), + pv: '$prebid.version$', + imuid: imuid, + ref: bidderRequest.refererInfo.referer + }) + + return { + method: 'POST', + url: `https://${bidDomain || BIDDER_DOMAIN}/bid/v1/sprebid`, + options: { + withCredentials: true + }, + data: data + }; } function interpretResponse(serverResponse, bidRequest) { @@ -105,34 +122,38 @@ function interpretResponse(serverResponse, bidRequest) { } const playerUrl = bidRequest.player || body.playerUrl; - const mediaType = bidRequest.mediaType || VIDEO; - - let bidResponse = { - requestId: bidRequest.bidId, - width: bidRequest.width, - height: bidRequest.height, - cpm: body.price, - currency: body.currency, - creativeId: body.creativeId, - dealId: body.dealId || '', - ttl: body.ttl || DEFAULT_TTL, - netRevenue: true, - mediaType: mediaType, - meta: { - advertiserDomains: body.adomain || [], - mediaType: VIDEO + + for (const res of body.ads) { + let bidResponse = { + requestId: res.bidId, + width: res.width, + height: res.height, + cpm: res.price, + currency: res.currency, + creativeId: res.creativeId, + dealId: body.dealId || '', + ttl: body.ttl || DEFAULT_TTL, + netRevenue: true, + mediaType: res.mediaType || VIDEO, + meta: { + advertiserDomains: res.adomain || [], + mediaType: VIDEO + } + }; + + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastXml = res.vast; + bidResponse.renderer = newRenderer(res.bidId, playerUrl); + } else { + const playerTag = createPlayerTag(playerUrl); + const renderTag = createRenderTag(res.width, res.height, res.vast); + bidResponse.ad = `
${playerTag}${renderTag}
`; } - }; - if (mediaType === VIDEO) { - bidResponse.vastXml = body.vast; - bidResponse.renderer = newRenderer(bidRequest.bidId, playerUrl); - } else { - const playerTag = createPlayerTag(playerUrl); - const renderTag = createRenderTag(bidRequest.width, bidRequest.height, body.vast); - bidResponse.ad = `
${playerTag}${renderTag}
`; + bidResponses.push(bidResponse); } - bidResponses.push(bidResponse); + // eslint-disable-next-line no-console + console.log(JSON.stringify(bidResponses)); return bidResponses; } diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index eace460eb22..0f62e988249 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -1,4 +1,4 @@ -import { isEmpty, deepAccess, isStr } from '../src/utils.js'; +import {isEmpty, 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'; @@ -52,17 +52,17 @@ export const spec = { videoData: raiGetVideoInfo(bid), scr_rsl: raiGetResolution(), cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null), - kws: (!isEmpty(bid.params.keywords) ? bid.params.keywords : null) + kws: (!isEmpty(bid.params.keywords) ? bid.params.keywords : null), + schain: bid.schain }; REFERER = (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null) payload.gdpr_consent = ''; - payload.gdpr = null; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - payload.gdpr = bidderRequest.gdprConsent.gdprApplies; } var payloadString = JSON.stringify(payload); @@ -296,7 +296,7 @@ function raiGetFloor(bid, config) { } else if (typeof bid.getFloor == 'function') { let floorSpec = bid.getFloor({ currency: config.getConfig('currency.adServerCurrency'), - mediaType: bid.mediaType.banner ? 'banner' : 'video', + mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video', size: '*' }) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 7f84dbd3344..70777fe03f2 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -159,7 +159,7 @@ function getSupplyChain(schainObject) { scStr += '!'; scStr += `${getEncodedValIfNotEmpty(node.asi)},`; scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${getEncodedValIfNotEmpty(node.hp)},`; + scStr += `${node.hp ? encodeURIComponent(node.hp) : ''},`; scStr += `${getEncodedValIfNotEmpty(node.rid)},`; scStr += `${getEncodedValIfNotEmpty(node.name)},`; scStr += `${getEncodedValIfNotEmpty(node.domain)}`; diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 2c562e5841a..40389b1e1e0 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -165,16 +165,26 @@ function mapBanner(slot) { * @returns {object} Site by OpenRTB 2.5 §3.2.13 */ function mapSite(slot, bidderRequest) { - const pubId = slot && slot.length > 0 - ? slot[0].params.publisherId - : 'unknown'; - return { + let pubId = 'unknown'; + let channel = null; + if (slot && slot.length > 0) { + pubId = slot[0].params.publisherId; + channel = slot[0].params.channel && + slot[0].params.channel + .toString() + .slice(0, 50); + } + let siteData = { publisher: { id: pubId.toString(), }, page: bidderRequest.refererInfo.referer, name: getOrigin() + }; + if (channel) { + siteData.channel = channel; } + return siteData; } /** diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index c5242c71946..6c88d66999a 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -98,6 +98,15 @@ * @param {UserConsentData} userConsent */ +/** + * @function? + * @summary on bid requested event + * @name RtdSubmodule#onBidRequestEvent + * @param {Object} data + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + /** * @interface ModuleConfig */ @@ -143,7 +152,7 @@ import {config} from '../../src/config.js'; import {module} from '../../src/hook.js'; -import { logError, logWarn } from '../../src/utils.js'; +import {logError, logInfo, logWarn} from '../../src/utils.js'; import events from '../../src/events.js'; import CONSTANTS from '../../src/constants.json'; import {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js'; @@ -164,13 +173,51 @@ let _dataProviders = []; let _userConsent; /** - * enable submodule in User ID + * Register a RTD submodule. + * * @param {RtdSubmodule} submodule + * @returns {function()} a de-registration function that will unregister the module when called. */ export function attachRealTimeDataProvider(submodule) { registeredSubModules.push(submodule); + return function detach() { + const idx = registeredSubModules.indexOf(submodule) + if (idx >= 0) { + registeredSubModules.splice(idx, 1); + initSubModules(); + } + } } +/** + * call each sub module event function by config order + */ +const setEventsListeners = (function () { + let registered = false; + return function setEventsListeners() { + if (!registered) { + Object.entries({ + [CONSTANTS.EVENTS.AUCTION_INIT]: ['onAuctionInitEvent'], + [CONSTANTS.EVENTS.AUCTION_END]: ['onAuctionEndEvent', getAdUnitTargeting], + [CONSTANTS.EVENTS.BID_RESPONSE]: ['onBidResponseEvent'], + [CONSTANTS.EVENTS.BID_REQUESTED]: ['onBidRequestEvent'] + }).forEach(([ev, [handler, preprocess]]) => { + events.on(ev, (args) => { + preprocess && preprocess(args); + subModules.forEach(sm => { + try { + sm[handler] && sm[handler](args, sm.config, _userConsent) + } catch (e) { + logError(`RTD provider '${sm.name}': error in '${handler}':`, e); + } + }); + }) + }); + registered = true; + } + } +})(); + export function init(config) { const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { if (!realTimeData.dataProviders) { @@ -209,22 +256,7 @@ function initSubModules() { } }); subModules = subModulesByOrder; -} - -/** - * call each sub module event function by config order - */ -function setEventsListeners() { - events.on(CONSTANTS.EVENTS.AUCTION_INIT, (args) => { - subModules.forEach(sm => { sm.onAuctionInitEvent && sm.onAuctionInitEvent(args, sm.config, _userConsent) }) - }); - events.on(CONSTANTS.EVENTS.AUCTION_END, (args) => { - getAdUnitTargeting(args); - subModules.forEach(sm => { sm.onAuctionEndEvent && sm.onAuctionEndEvent(args, sm.config, _userConsent) }) - }); - events.on(CONSTANTS.EVENTS.BID_RESPONSE, (args) => { - subModules.forEach(sm => { sm.onBidResponseEvent && sm.onBidResponseEvent(args, sm.config, _userConsent) }) - }); + logInfo(`Real time data module enabled, using submodules: ${subModules.map((m) => m.name).join(', ')}`); } /** @@ -259,18 +291,12 @@ export function setBidRequestsData(fn, reqBidsConfigObj) { return exitHook(); } - if (shouldDelayAuction) { - waitTimeout = setTimeout(exitHook, _moduleConfig.auctionDelay); - } + waitTimeout = setTimeout(exitHook, shouldDelayAuction ? _moduleConfig.auctionDelay : 0); relevantSubModules.forEach(sm => { sm.getBidRequestData(reqBidsConfigObj, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent) }); - if (!shouldDelayAuction) { - return exitHook(); - } - function onGetBidRequestDataCallback() { if (isDone) { return; @@ -278,12 +304,15 @@ export function setBidRequestsData(fn, reqBidsConfigObj) { if (this.config && this.config.waitForIt) { callbacksExpected--; } - if (callbacksExpected <= 0) { - return exitHook(); + if (callbacksExpected === 0) { + setTimeout(exitHook, 0); } } function exitHook() { + if (isDone) { + return; + } isDone = true; clearTimeout(waitTimeout); fn.call(this, reqBidsConfigObj); diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js index 5b17048cbd3..0bebbecedb2 100644 --- a/modules/rubiconAnalyticsAdapter.js +++ b/modules/rubiconAnalyticsAdapter.js @@ -384,8 +384,9 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) { 'floorRuleValue', () => deepAccess(bid, 'floorData.floorRuleValue'), 'floorRule', () => debugTurnedOn() ? deepAccess(bid, 'floorData.floorRule') : undefined, 'adomains', () => { - let adomains = deepAccess(bid, 'meta.advertiserDomains'); - return Array.isArray(adomains) && adomains.length > 0 ? adomains.slice(0, 10) : undefined + const adomains = deepAccess(bid, 'meta.advertiserDomains'); + const validAdomains = Array.isArray(adomains) && adomains.filter(domain => typeof domain === 'string'); + return validAdomains && validAdomains.length > 0 ? validAdomains.slice(0, 10) : undefined } ]); } diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 501ccc98e33..28e491900f8 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1,4 +1,4 @@ -import { mergeDeep, _each, logError, deepAccess, deepSetValue, isStr, isNumber, logWarn, convertTypes, isArray, parseSizesInput, logMessage } from '../src/utils.js'; +import { mergeDeep, _each, logError, deepAccess, deepSetValue, isStr, isNumber, logWarn, convertTypes, isArray, parseSizesInput, logMessage, formatQS } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -765,21 +765,23 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { if (!hasSynced && syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to syncEndpoint - let params = ''; + let params = {}; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - // add 'gdpr' only if 'gdprApplies' is defined + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `?gdpr_consent=${gdprConsent.consentString}`; + params['gdpr'] = Number(gdprConsent.gdprApplies); + } + if (typeof gdprConsent.consentString === 'string') { + params['gdpr_consent'] = gdprConsent.consentString; } } if (uspConsent) { - params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + params['us_privacy'] = encodeURIComponent(uspConsent); } + params = Object.keys(params).length ? `?${formatQS(params)}` : ''; + hasSynced = true; return { type: 'iframe', @@ -992,6 +994,8 @@ function applyFPD(bidRequest, mediaType, data) { let fpd = mergeDeep({}, config.getConfig('ortb2') || {}, BID_FPD); let impData = deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; + + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); const SEGTAX = {user: [4], site: [1, 2, 5, 6]}; const MAP = {user: 'tg_v.', site: 'tg_i.', adserver: 'tg_i.dfp_ad_unit_code', pbadslot: 'tg_i.pbadslot', keywords: 'kw'}; const validate = function(prop, key, parentName) { @@ -1020,16 +1024,6 @@ function applyFPD(bidRequest, mediaType, data) { data[loc] = (data[loc]) ? data[loc].concat(',', val) : val; } - Object.keys(impData).forEach((key) => { - if (key === 'adserver') { - ['name', 'adslot'].forEach(prop => { - if (impData[key][prop]) impData[key][prop] = impData[key][prop].toString().replace(/^\/+/, ''); - }); - } else if (key === 'pbadslot') { - impData[key] = impData[key].toString().replace(/^\/+/, ''); - } - }); - if (mediaType === BANNER) { ['site', 'user'].forEach(name => { Object.keys(fpd[name]).forEach((key) => { @@ -1045,12 +1039,30 @@ function applyFPD(bidRequest, mediaType, data) { }); }); Object.keys(impData).forEach((key) => { - (key === 'adserver') ? addBannerData(impData[key].adslot, name, key) : addBannerData(impData[key], 'site', key); + if (key !== 'adserver') { + addBannerData(impData[key], 'site', key); + } else if (impData[key].name === 'gam') { + addBannerData(impData[key].adslot, name, key) + } }); + + // add in gpid + if (gpid) { + data['p_gpid'] = gpid; + } + + // only send one of pbadslot or dfp adunit code (prefer pbadslot) + if (data['tg_i.pbadslot']) { + delete data['tg_i.dfp_ad_unit_code']; + } } else { if (Object.keys(impData).length) { mergeDeep(data.imp[0].ext, {data: impData}); } + // add in gpid + if (gpid) { + data.imp[0].ext.gpid = gpid; + } mergeDeep(data, fpd); } diff --git a/modules/saambaaBidAdapter.js b/modules/saambaaBidAdapter.js new file mode 100644 index 00000000000..2810853532d --- /dev/null +++ b/modules/saambaaBidAdapter.js @@ -0,0 +1,420 @@ +import { deepAccess, isFn, generateUUID, parseUrl, isEmpty, parseSizesInput } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import find from 'core-js-pure/features/array/find.js'; +import includes from 'core-js-pure/features/array/includes.js'; + +const ADAPTER_VERSION = '1.0'; +const BIDDER_CODE = 'saambaa'; + +export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip', 'playerSize', 'context']; +export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; + +let pubid = ''; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + + isBidRequestValid(bidRequest) { + if (typeof bidRequest != 'undefined') { + if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; } + if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; } + return true; + } else { return false; } + }, + + buildRequests(bids, bidderRequest) { + let requests = []; + let videoBids = bids.filter(bid => isVideoBidValid(bid)); + let bannerBids = bids.filter(bid => isBannerBidValid(bid)); + videoBids.forEach(bid => { + pubid = getVideoBidParam(bid, 'pubid'); + requests.push({ + method: 'POST', + url: VIDEO_ENDPOINT + pubid, + data: createVideoRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + + bannerBids.forEach(bid => { + pubid = getBannerBidParam(bid, 'pubid'); + + requests.push({ + method: 'POST', + url: BANNER_ENDPOINT + pubid, + data: createBannerRequestData(bid, bidderRequest), + bidRequest: bid + }); + }); + return requests; + }, + + interpretResponse(serverResponse, {bidRequest}) { + let response = serverResponse.body; + if (response !== null && isEmpty(response) == false) { + if (isVideoBid(bidRequest)) { + let bidResponse = { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, + mediaType: VIDEO, + netRevenue: true + } + + if (response.seatbid[0].bid[0].adm) { + bidResponse.vastXml = response.seatbid[0].bid[0].adm; + bidResponse.adResponse = { + content: response.seatbid[0].bid[0].adm + }; + } else { + bidResponse.vastUrl = response.seatbid[0].bid[0].nurl; + } + + return bidResponse; + } else { + return { + requestId: response.id, + bidderCode: BIDDER_CODE, + cpm: response.seatbid[0].bid[0].price, + width: response.seatbid[0].bid[0].w, + height: response.seatbid[0].bid[0].h, + ad: response.seatbid[0].bid[0].adm, + ttl: response.seatbid[0].bid[0].ttl || 60, + creativeId: response.seatbid[0].bid[0].crid, + currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, + mediaType: BANNER, + netRevenue: true + } + } + } + } +}; + +function isBannerBid(bid) { + return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +function isVideoBid(bid) { + return deepAccess(bid, 'mediaTypes.video'); +} + +function getBannerBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {}; + return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); +} + +function getVideoBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {}; + return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); +} + +function isVideoBidValid(bid) { + return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); +} + +function isBannerBidValid(bid) { + return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); +} + +function getVideoBidParam(bid, key) { + return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); +} + +function getBannerBidParam(bid, key) { + return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); +} + +function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function getDoNotTrack() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} + +function findAndFillParam(o, key, value) { + try { + if (typeof value === 'function') { + o[key] = value(); + } else { + o[key] = value; + } + } catch (ex) {} +} + +function getOsVersion() { + let clientStrings = [ + { s: 'Android', r: /Android/ }, + { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, + { s: 'Mac OS X', r: /Mac OS X/ }, + { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, + { s: 'Linux', r: /(Linux|X11)/ }, + { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, + { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, + { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, + { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, + { s: 'Windows Vista', r: /Windows NT 6.0/ }, + { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, + { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, + { s: 'UNIX', r: /UNIX/ }, + { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } + ]; + let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); + return cs ? cs.s : 'unknown'; +} + +function getFirstSize(sizes) { + return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; +} + +function parseSizes(sizes) { + return parseSizesInput(sizes).map(size => { + let [ width, height ] = size.split('x'); + return { + w: parseInt(width, 10) || undefined, + h: parseInt(height, 10) || undefined + }; + }); +} + +function getVideoSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +function getBannerSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return ''; + } +} + +function getVideoTargetingParams(bid) { + const result = {}; + const excludeProps = ['playerSize', 'context', 'w', 'h']; + Object.keys(Object(bid.mediaTypes.video)) + .filter(key => !includes(excludeProps, 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 ]; + }); + return result; +} + +function createVideoRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + // if size is explicitly given via adapter params + let paramSize = getVideoBidParam(bid, 'size'); + let sizes = []; + let coppa = config.getConfig('coppa'); + + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getVideoSizes(bid); + } + const firstSize = getFirstSize(sizes); + let floor = (getVideoBidFloor(bid) == null || typeof getVideoBidFloor(bid) == 'undefined') ? 0.5 : getVideoBidFloor(bid); + let video = getVideoTargetingParams(bid); + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1, + 'os': getOsVersion() + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getVideoBidParam(bid, 'placement'); + + for (let j = 0; j < sizes.length; j++) { + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + 'video': Object.assign({ + 'id': generateUUID(), + 'pos': 0, + 'w': firstSize.w, + 'h': firstSize.h, + 'mimes': DEFAULT_MIMES + }, video) + + }); + } + if (coppa) { + o.regs.ext = {'coppa': 1}; + } + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} + +function getTopWindowLocation(bidderRequest) { + let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + return parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true }); +} + +function createBannerRequestData(bid, bidderRequest) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(); + + // if size is explicitly given via adapter params + + let paramSize = getBannerBidParam(bid, 'size'); + let sizes = []; + let coppa = config.getConfig('coppa'); + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getBannerSizes(bid); + } + + let floor = (getBannerBidFloor(bid) == null || typeof getBannerBidFloor(bid) == 'undefined') ? 0.1 : getBannerBidFloor(bid); + const o = { + 'device': { + 'langauge': (global.navigator.language).split('-')[0], + 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), + 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, + 'js': 1 + }, + 'at': 2, + 'site': {}, + 'tmax': 3000, + 'cur': ['USD'], + 'id': bid.bidId, + 'imp': [], + 'regs': { + 'ext': { + } + }, + 'user': { + 'ext': { + } + } + }; + + o.site['page'] = topLocation.href; + o.site['domain'] = topLocation.hostname; + o.site['search'] = topLocation.search; + o.site['domain'] = topLocation.hostname; + o.site['ref'] = topReferrer; + o.site['mobile'] = isMobile() ? 1 : 0; + const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; + + o.device['dnt'] = getDoNotTrack() ? 1 : 0; + + findAndFillParam(o.site, 'name', function() { + return global.top.document.title; + }); + + findAndFillParam(o.device, 'h', function() { + return global.screen.height; + }); + findAndFillParam(o.device, 'w', function() { + return global.screen.width; + }); + + let placement = getBannerBidParam(bid, 'placement'); + for (let j = 0; j < sizes.length; j++) { + let size = sizes[j]; + + o.imp.push({ + 'id': '' + j, + 'displaymanager': '' + BIDDER_CODE, + 'displaymanagerver': '' + ADAPTER_VERSION, + 'tagId': placement, + 'bidfloor': floor, + 'bidfloorcur': 'USD', + 'secure': secure, + 'banner': { + 'id': generateUUID(), + 'pos': 0, + 'w': size['w'], + 'h': size['h'] + } + }); + } + if (coppa) { + o.regs.ext = {'coppa': 1}; + } + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} +registerBidder(spec); diff --git a/modules/saambaaBidAdapter.md b/modules/saambaaBidAdapter.md index 2d391da7628..d58e3f0abfa 100755 --- a/modules/saambaaBidAdapter.md +++ b/modules/saambaaBidAdapter.md @@ -1,69 +1,66 @@ -# Overview - -``` -Module Name: Saambaa Bidder Adapter -Module Type: Bidder Adapter -Maintainer: matt.voigt@saambaa.com -``` - -# Description - -Connects to Saambaa exchange for bids. - -Saambaa 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: 'saambaa', - 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: 'saambaa', - params: { - pubid: '121ab139faf7ac67428a23f1d0a9a71b', - placement: 1234, - size: "320x480", - video: { - id: 123, - skip: 1, - mimes : ['video/mp4', 'application/javascript'], - playbackmethod : [2,6], - maxduration: 30 - } - } - } - ] - }; +# Overview + +``` +Module Name: Saambaa Bidder Adapter +Module Type: Bidder Adapter +Maintainer: matt.voigt@saambaa.com +``` + +# Description + +Connects to Saambaa exchange for bids. + +Saambaa 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: 'saambaa', + 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', + skip: 1, + mimes : ['video/mp4', 'application/javascript'], + playbackmethod : [2,6], + maxduration: 30 + } + }, + bids: [ + { + bidder: 'saambaa', + params: { + pubid: '121ab139faf7ac67428a23f1d0a9a71b', + placement: 1234, + size: "320x480" + } + } + ] + }; ``` \ No newline at end of file diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index 00f3b64fb44..b7aec0f8881 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -152,10 +152,10 @@ export const spec = { const { seatbid, cur } = serverResponse.body; - const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + 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) => { @@ -167,7 +167,7 @@ export const spec = { cpm: bidResponse.price, creativeId: bidResponse.crid, ttl: 1000, - netRevenue: bid.netRevenue === 'net', + netRevenue: (!bid.netRevenue || bid.netRevenue === 'net'), currency: cur, mediaType: NATIVE, bidderCode: BIDDER_CODE, diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index cb646fe10c3..bae27d41028 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -13,6 +13,11 @@ const ALLOWED_PLACEMENTS = { banner: true, video: true } + +// Global Vendor List Id +// https://iabeurope.eu/vendor-list-tcf-v2-0/ +const GVLID = 157; + const mediaTypesMap = { [BANNER]: 'display', [VIDEO]: 'video' @@ -158,6 +163,7 @@ export function getTimeoutUrl (data) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: [SEEDTAG_ALIAS], supportedMediaTypes: [BANNER, VIDEO], /** diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 0c25a1747f6..32a96100d43 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -11,7 +11,7 @@ import { coppaDataHandler } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; const GVLID = 887; -const storage = getStorageManager(GVLID, 'pubCommonId'); +export const storage = getStorageManager(GVLID, 'pubCommonId'); const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; const OPTOUT_NAME = '_pubcid_optout'; @@ -171,7 +171,33 @@ export const sharedIdSystemSubmodule = { return {id: storedId}; } } + }, + + domainOverride: function () { + const domainElements = document.domain.split('.'); + const cookieName = `_gd${Date.now()}`; + for (let i = 0, topDomain, testCookie; i < domainElements.length; i++) { + const nextDomain = domainElements.slice(i).join('.'); + + // write test cookie + storage.setCookie(cookieName, '1', undefined, undefined, nextDomain); + + // read test cookie to verify domain was valid + testCookie = storage.getCookie(cookieName); + + // delete test cookie + storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain); + + if (testCookie === '1') { + // cookie was written successfully using test domain so the topDomain is updated + topDomain = nextDomain; + } else { + // cookie failed to write using test domain so exit by returning the topDomain + return topDomain; + } + } } + }; submodule('userId', sharedIdSystemSubmodule); diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 36d1a94e93d..06cc81324cf 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,10 +1,10 @@ -import { generateUUID, deepAccess, inIframe } from '../src/utils.js'; +import { deepAccess, generateUUID, inIframe } 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 { createEidsArray } from './userId/eids.js'; -const VERSION = '4.0.1'; +const VERSION = '4.1.0'; const BIDDER_CODE = 'sharethrough'; const SUPPLY_ID = 'WYu2BXv1'; @@ -23,6 +23,7 @@ export const sharethroughAdapterSpec = { buildRequests: (bidRequests, bidderRequest) => { const timeout = config.getConfig('bidderTimeout'); + const firstPartyData = config.getConfig('ortb2') || {}; const nonHttp = sharethroughInternal.getProtocol().indexOf('http') < 0; const secure = nonHttp || (sharethroughInternal.getProtocol().indexOf('https') > -1); @@ -35,12 +36,8 @@ export const sharethroughAdapterSpec = { site: { domain: window.location.hostname, page: window.location.href, - ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null, - }, - user: { - ext: { - eids: userIdAsEids(bidRequests[0]), - }, + ref: deepAccess(bidderRequest, 'refererInfo.referer'), + ...firstPartyData.site, }, device: { ua: navigator.userAgent, @@ -66,6 +63,10 @@ export const sharethroughAdapterSpec = { test: 0, }; + req.user = nullish(firstPartyData.user, {}); + if (!req.user.ext) req.user.ext = {}; + req.user.ext.eids = userIdAsEids(bidRequests[0]); + if (bidderRequest.gdprConsent) { const gdprApplies = bidderRequest.gdprConsent.gdprApplies === true; req.regs.ext.gdpr = gdprApplies ? 1 : 0; @@ -86,15 +87,9 @@ export const sharethroughAdapterSpec = { impression.ext = { gpid: gpid }; } - // if request is for video, we only support instream - if (bidReq.mediaTypes && bidReq.mediaTypes.video && bidReq.mediaTypes.video.context === 'outstream') { - // return null so we can easily remove this imp from the array of imps that we send to adserver - return null; - } - - if (bidReq.mediaTypes && bidReq.mediaTypes.video) { - const videoRequest = bidReq.mediaTypes.video; + const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); + if (videoRequest) { // default playerSize, only change this if we know width and height are properly defined in the request let [w, h] = [640, 360]; if (videoRequest.playerSize && videoRequest.playerSize[0] && videoRequest.playerSize[1]) { @@ -117,9 +112,9 @@ export const sharethroughAdapterSpec = { startdelay: nullish(videoRequest.startdelay, 0), skipmin: nullish(videoRequest.skipmin, 0), skipafter: nullish(videoRequest.skipafter, 0), + placement: videoRequest.context === 'instream' ? 1 : +deepAccess(videoRequest, 'placement', 4), }; - if (videoRequest.placement) impression.video.placement = videoRequest.placement; if (videoRequest.delivery) impression.video.delivery = videoRequest.delivery; if (videoRequest.companiontype) impression.video.companiontype = videoRequest.companiontype; if (videoRequest.companionad) impression.video.companionad = videoRequest.companionad; diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index 99378b494df..4c8fb812edc 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -61,15 +61,24 @@ export const spec = { } } + const consentData = bidderRequest.gdprConsent || {}; + + const gdprConsent = { + apiVersion: consentData.apiVersion || 2, + gdprApplies: consentData.gdprApplies || 0, + consentString: consentData.consentString || '', + } + return { type: streamType, + adUnitCode: bid.adUnitCode, bidId: bid.bidId, mediaType: type, context: context, playerId: getBidIdParameter('playerId', bid.params), auctionId: bidderRequest.auctionId, bidderCode: BIDDER_CODE, - gdprConsent: bidderRequest.gdprConsent, + gdprConsent: gdprConsent, start: +new Date(), timeout: 3000, size: { @@ -159,6 +168,7 @@ function createBids(bidRes, reqData) { let bidUnit = {}; bidUnit.cpm = bid.cpm; bidUnit.requestId = bid.bidId; + bidUnit.adUnitCode = reqBid.adUnitCode; bidUnit.currency = bid.currency; bidUnit.mediaType = bid.mediaType || VIDEO; bidUnit.ttl = TTL; @@ -183,7 +193,8 @@ function createBids(bidRes, reqData) { } else if (bid.context === 'outstream') { const renderer = Renderer.install({ id: bid.bidId, - url: '//', + url: 'https://static.showheroes.com/renderer.js', + adUnitCode: reqBid.adUnitCode, config: { playerId: reqBid.playerId, width: bid.size.width, diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index dd389b42098..68334aed0ab 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -5,7 +5,7 @@ import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.4' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.6' const CURRENCY = 'USD'; const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { @@ -37,6 +37,11 @@ const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { user: { ext: {} }, + source: { + ext: { + schain: bidRequest.schain + } + }, ext: { client: SMAATO_CLIENT } @@ -299,6 +304,7 @@ function createBannerImp(bidRequest) { id: bidRequest.bidId, tagid: deepAccess(bidRequest, 'params.adspaceId'), bidfloor: getBidFloor(bidRequest, BANNER, adUnitSizes), + instl: deepAccess(bidRequest.ortb2Imp, 'instl'), banner: { w: sizes[0].w, h: sizes[0].h, @@ -314,6 +320,7 @@ function createVideoImp(bidRequest, videoMediaType) { id: bidRequest.bidId, tagid: deepAccess(bidRequest, 'params.adspaceId'), bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), + instl: deepAccess(bidRequest.ortb2Imp, 'instl'), video: { mimes: videoMediaType.mimes, minduration: videoMediaType.minduration, @@ -341,6 +348,7 @@ function createAdPodImp(bidRequest, videoMediaType) { id: bidRequest.bidId, tagid: tagid, bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), + instl: deepAccess(bidRequest.ortb2Imp, 'instl'), video: { w: videoMediaType.playerSize[0][0], h: videoMediaType.playerSize[0][1], diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index da63331cd0f..00c962445d9 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -161,11 +161,20 @@ export const spec = { domain: domain, publisher: { id: publisherId + }, + content: { + ext: { + prebid: { + name: 'pbjs', + version: '$prebid.version$' + } + } } }, device: device, at: at, - cur: cur + cur: cur, + ext: {} }; const userExt = {}; @@ -194,6 +203,8 @@ export const spec = { }; } + // requestPayload.user.ext.ver = pbjs.version; + // Targeting if (getBidIdParameter('data', bid.params.user)) { var targetingarr = []; @@ -336,6 +347,7 @@ function createOutstreamConfig(bid) { let confTitle = getBidIdParameter('title', bid.renderer.config.outstream_options); let confSkipOffset = getBidIdParameter('skipOffset', bid.renderer.config.outstream_options); let confDesiredBitrate = getBidIdParameter('desiredBitrate', bid.renderer.config.outstream_options); + let confVisibilityThreshold = getBidIdParameter('visibilityThreshold', bid.renderer.config.outstream_options); let elementId = getBidIdParameter('slot', bid.renderer.config.outstream_options) || bid.adUnitCode; logMessage('[SMARTX][renderer] Handle SmartX outstream renderer'); @@ -384,6 +396,10 @@ function createOutstreamConfig(bid) { smartPlayObj.desiredBitrate = confDesiredBitrate; } + if (confVisibilityThreshold) { + smartPlayObj.visibilityThreshold = confVisibilityThreshold; + } + smartPlayObj.adResponse = bid.vastContent; const divID = '[id="' + elementId + '"]'; diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index b4caf1db1d0..c8f3c138ce4 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -9,11 +9,13 @@ const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync'; const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify'; const TMAX = 450; -const BIDDER_VERSION = '5.2'; +const BIDDER_VERSION = '5.3'; const W = window; const { navigator } = W; const oneCodeDetection = {}; +const adUnitsCalled = {}; const adSizesCalled = {}; +const pageView = {}; var consentApiVersion; /** @@ -70,6 +72,7 @@ const cookieSupport = () => { }; const applyClientHints = ortbRequest => { + const { location } = document; const { connection = {}, deviceMemory, userAgentData = {} } = navigator; const viewport = W.visualViewport || false; const segments = []; @@ -85,6 +88,16 @@ const applyClientHints = ortbRequest => { 'CH-isMobile': userAgentData.mobile, }; + /** + Check / generate page view id + Should be generated dureing first call to applyClientHints(), + and re-generated if pathname has changed + */ + if (!pageView.id || location.pathname !== pageView.path) { + pageView.path = location.pathname; + pageView.id = Math.floor(1E20 * Math.random()); + } + Object.keys(hints).forEach(key => { const hint = hints[key]; @@ -100,6 +113,14 @@ const applyClientHints = ortbRequest => { id: '12', name: 'NetInfo', segment: segments, + }, { + id: '7', + name: 'pvid', + segment: [ + { + value: `${pageView.id}` + } + ] }]; ortbRequest.user = Object.assign(ortbRequest.user, { data }); @@ -129,6 +150,7 @@ const setOnAny = (collection, key) => collection.reduce((prev, next) => prev || */ const sendNotification = payload => { ajax(NOTIFY_URL, null, JSON.stringify(payload), { + contentType: 'application/json', withCredentials: false, method: 'POST', crossOrigin: true @@ -267,8 +289,14 @@ const mapImpression = slot => { send this info as ext.pbsize */ const slotSize = slot.sizes.length ? slot.sizes.reduce((prev, next) => prev[0] * prev[1] <= next[0] * next[1] ? next : prev).join('x') : '1x1'; - adSizesCalled[slotSize] = adSizesCalled[slotSize] ? adSizesCalled[slotSize] += 1 : 1; - ext.data = Object.assign({ pbsize: `${slotSize}_${adSizesCalled[slotSize]}` }, ext.data); + + if (!adUnitsCalled[adUnitCode]) { + // this is a new adunit - assign & save pbsize + adSizesCalled[slotSize] = adSizesCalled[slotSize] ? adSizesCalled[slotSize] += 1 : 1; + adUnitsCalled[adUnitCode] = `${slotSize}_${adSizesCalled[slotSize]}` + } + + ext.data = Object.assign({ pbsize: adUnitsCalled[adUnitCode] }, ext.data); const imp = { id: id && siteId ? id : 'bidid-' + bidId, @@ -325,6 +353,9 @@ const parseNative = nativeData => { case 0: result.title = asset.title.text; break; + case 1: + result.cta = asset.data.value; + break; case 2: result.icon = { url: asset.img.url, diff --git a/modules/synacormediaBidAdapter.js b/modules/synacormediaBidAdapter.js index 7694f5d838e..96616bb6a48 100644 --- a/modules/synacormediaBidAdapter.js +++ b/modules/synacormediaBidAdapter.js @@ -1,6 +1,6 @@ 'use strict'; -import { getAdUnitSizes, logWarn, deepSetValue } from '../src/utils.js'; +import { getAdUnitSizes, logWarn, deepSetValue, isFn, isPlainObject } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -67,11 +67,7 @@ export const spec = { } else { seatId = bid.params.seatId; } - const tagIdOrplacementId = bid.params.tagId || bid.params.placementId; - const bidFloor = bid.params.bidfloor ? parseFloat(bid.params.bidfloor) : null; - if (isNaN(bidFloor)) { - logWarn(`Synacormedia: there is an invalid bid floor: ${bid.params.bidfloor}`); - } + const tagIdOrPlacementId = bid.params.tagId || bid.params.placementId; let pos = parseInt(bid.params.pos, 10); if (isNaN(pos)) { logWarn(`Synacormedia: there is an invalid POS: ${bid.params.pos}`); @@ -83,9 +79,9 @@ export const spec = { let imps = []; if (videoOrBannerKey === 'banner') { - imps = this.buildBannerImpressions(adSizes, bid, tagIdOrplacementId, pos, bidFloor, videoOrBannerKey); + imps = this.buildBannerImpressions(adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey); } else if (videoOrBannerKey === 'video') { - imps = this.buildVideoImpressions(adSizes, bid, tagIdOrplacementId, pos, bidFloor, videoOrBannerKey); + imps = this.buildVideoImpressions(adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey); } if (imps.length > 0) { imps.forEach(i => openRtbBidRequest.imp.push(i)); @@ -128,7 +124,7 @@ export const spec = { return eids; }, - buildBannerImpressions: function (adSizes, bid, tagIdOrPlacementId, pos, bidFloor, videoOrBannerKey) { + buildBannerImpressions: function (adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey) { let format = []; let imps = []; adSizes.forEach((size, i) => { @@ -151,6 +147,10 @@ export const spec = { }, tagid: tagIdOrPlacementId, }; + const bidFloor = getBidFloor(bid, 'banner', '*'); + if (isNaN(bidFloor)) { + logWarn(`Synacormedia: there is an invalid bid floor: ${bid.params.bidfloor}`); + } if (bidFloor !== null && !isNaN(bidFloor)) { imp.bidfloor = bidFloor; } @@ -159,7 +159,7 @@ export const spec = { return imps; }, - buildVideoImpressions: function(adSizes, bid, tagIdOrPlacementId, pos, bidFloor, videoOrBannerKey) { + buildVideoImpressions: function(adSizes, bid, tagIdOrPlacementId, pos, videoOrBannerKey) { let imps = []; adSizes.forEach((size, i) => { if (!size || size.length != 2) { @@ -171,6 +171,11 @@ export const spec = { id: `${videoOrBannerKey.substring(0, 1)}${bid.bidId}-${size0}x${size1}`, tagid: tagIdOrPlacementId }; + const bidFloor = getBidFloor(bid, 'video', size); + if (isNaN(bidFloor)) { + logWarn(`Synacormedia: there is an invalid bid floor: ${bid.params.bidfloor}`); + } + if (bidFloor !== null && !isNaN(bidFloor)) { imp.bidfloor = bidFloor; } @@ -287,4 +292,20 @@ export const spec = { } }; +function getBidFloor(bid, mediaType, size) { + if (!isFn(bid.getFloor)) { + return bid.params.bidfloor ? parseFloat(bid.params.bidfloor) : null; + } + let floor = bid.getFloor({ + currency: 'USD', + mediaType, + size + }); + + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + registerBidder(spec); diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index a026b2cd6a6..769e8f73565 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -7,9 +7,10 @@ import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; const BIDDER_CODE = 'tappx'; +const GVLID_CODE = 628; const TTL = 360; const CUR = 'USD'; -const TAPPX_BIDDER_VERSION = '0.1.1004'; +const TAPPX_BIDDER_VERSION = '0.1.1005'; const TYPE_CNN = 'prebidjs'; const LOG_PREFIX = '[TAPPX]: '; const VIDEO_SUPPORT = ['instream', 'outstream']; @@ -42,6 +43,7 @@ var hostDomain; export const spec = { code: BIDDER_CODE, + gvlid: GVLID_CODE, supportedMediaTypes: [BANNER, VIDEO], /** @@ -51,7 +53,14 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { - return validBasic(bid) && validMediaType(bid) + // bid.params.host + if ((new RegExp(`^(vz.*|zz.*)\\.*$`, 'i')).test(bid.params.host)) { // New endpoint + if ((new RegExp(`^(zz.*)\\.*$`, 'i')).test(bid.params.host)) return validBasic(bid) + else return validBasic(bid) && validMediaType(bid) + } else { // This is backward compatible feature. It will be remove in the future + if ((new RegExp(`^(ZZ.*)\\.*$`, 'i')).test(bid.params.endpoint)) return validBasic(bid) + else return validBasic(bid) && validMediaType(bid) + } }, /** @@ -167,10 +176,6 @@ function validMediaType(bid) { logWarn(LOG_PREFIX, 'Please review the mandatory Tappx parameters for Video. Video context not supported.'); return false; } - if (typeof video.mimes == 'undefined') { - logWarn(LOG_PREFIX, 'Please review the mandatory Tappx parameters for Video. Mimes param is mandatory.'); - return false; - } } return true; diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js new file mode 100644 index 00000000000..5714916c131 --- /dev/null +++ b/modules/targetVideoBidAdapter.js @@ -0,0 +1,187 @@ +import find from 'core-js-pure/features/array/find.js'; +import { getBidRequest } from '../src/utils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const SOURCE = 'pbjs'; +const BIDDER_CODE = 'targetVideo'; +const ENDPOINT_URL = 'https://ib.adnxs.com/ut/v3/prebid'; +const MARGIN = 1.35; + +export const spec = { + + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid && bid.params && bid.params.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + const tags = bidRequests.map(createVideoTag); + const schain = bidRequests[0].schain; + const payload = { + tags: tags, + sdk: { + source: SOURCE, + version: '$prebid.version$' + }, + schain: schain + }; + return formatRequest(payload, bidderRequest); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, { bidderRequest }) { + serverResponse = serverResponse.body; + const bids = []; + + if (serverResponse.tags) { + serverResponse.tags.forEach(serverBid => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid && rtbBid.cpm !== 0 && rtbBid.ad_type == VIDEO) { + bids.push(newBid(serverBid, rtbBid, bidderRequest)); + } + }); + } + + return bids; + } + +} + +function getSizes(request) { + let sizes = request.sizes; + if (!sizes && request.mediaTypes && request.mediaTypes.banner && request.mediaTypes.banner.sizes) { + sizes = request.mediaTypes.banner.sizes; + } + if (Array.isArray(sizes) && !Array.isArray(sizes[0])) { + sizes = [sizes[0], sizes[1]]; + } + if (!Array.isArray(sizes) || !Array.isArray(sizes[0])) { + sizes = [[0, 0]]; + } + + return sizes; +} + +function formatRequest(payload, bidderRequest) { + const options = { + withCredentials: true + }; + const request = { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(payload), + bidderRequest, + options + }; + + return request; +} + +/** + * Create video auction. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ +function createVideoTag(bid) { + const tag = {}; + tag.id = parseInt(bid.params.placementId, 10); + tag.gpid = 'targetVideo'; + tag.sizes = getSizes(bid); + tag.primary_size = tag.sizes[0]; + tag.ad_types = [VIDEO]; + tag.uuid = bid.bidId; + tag.allow_smaller_sizes = false; + tag.use_pmt_rule = false + tag.prebid = true; + tag.disable_psa = true; + tag.hb_source = 1; + tag.require_asset_url = true; + tag.video = { + playback_method: 2, + skippable: true + }; + + return tag; +} + +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, rtbBid, bidderRequest) { + const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); + const sizes = getSizes(bidRequest); + const bid = { + requestId: serverBid.uuid, + cpm: rtbBid.cpm * MARGIN, + creativeId: rtbBid.creative_id, + dealId: rtbBid.deal_id, + currency: 'USD', + netRevenue: true, + width: sizes[0][0], + height: sizes[0][1], + ttl: 300, + adUnitCode: bidRequest.adUnitCode, + appnexus: { + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code + } + }; + + if (rtbBid.rtb.video) { + Object.assign(bid, { + vastImpUrl: rtbBid.notify_url, + ad: getBannerHtml(rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url)), + ttl: 3600 + }); + } + + return bid; +} + +function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); +} + +function getBannerHtml(vastUrl) { + return ` + + + + + + + +
+ + + + `; +} + +registerBidder(spec); diff --git a/modules/targetVideoBidAdapter.md b/modules/targetVideoBidAdapter.md new file mode 100644 index 00000000000..557c9f94410 --- /dev/null +++ b/modules/targetVideoBidAdapter.md @@ -0,0 +1,34 @@ +# Overview + +``` +Module Name: Target Video Bid Adapter +Module Type: Bidder Adapter +Maintainer: grajzer@gmail.com +``` + +# Description + +Connects to Appnexus exchange for bids. + +TargetVideo bid adapter supports Banner. + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[640, 480], [300, 250]], + } + }, + bids: [{ + bidder: 'targetVideo', + params: { + placementId: 13232361 + } + }] + } +]; +``` diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index ea581905883..4bea8858d98 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -190,8 +190,7 @@ function buildRequestObject(bid) { const reqObj = {}; let placementId = getValue(bid.params, 'placementId'); let pageId = getValue(bid.params, 'pageId'); - const impressionData = deepAccess(bid, 'ortb2Imp.ext.data'); - const gpid = deepAccess(impressionData, 'pbadslot') || deepAccess(impressionData, 'adserver.adslot'); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid'); reqObj.sizes = getSizes(bid); reqObj.bidId = getBidIdParameter('bidId', bid); diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js index 560bf762394..c55ecdcfbc6 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -2,7 +2,7 @@ import { logError, isEmpty, deepAccess, triggerPixel, logWarn, isArray } from '. import {createBid as createBidFactory} from '../src/bidfactory.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {VIDEO} from '../src/mediaTypes.js'; -import {STATUS} from '../src/constants.json'; +import CONSTANTS from '../src/constants.json'; const BIDDER_CODE = 'telaria'; const DOMAIN = 'tremorhub.com'; @@ -86,7 +86,7 @@ export const spec = { logError(errorMessage); } else if (!isEmpty(bidResult.seatbid)) { bidResult.seatbid[0].bid.forEach(tag => { - bids.push(createBid(STATUS.GOOD, bidderRequest, tag, width, height, BIDDER_CODE)); + bids.push(createBid(CONSTANTS.STATUS.GOOD, bidderRequest, tag, width, height, BIDDER_CODE)); }); } diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index 9907d1b2ff4..31be2814bba 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -6,9 +6,8 @@ import { config } from '../src/config.js'; const BIDDER_CODE = 'trustx'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson?sp=trustx'; -const ADDITIONAL_SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; -const ADAPTER_SYNC_URL = 'https://sofia.trustx.org/push_sync'; +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 = { @@ -76,7 +75,7 @@ export const spec = { if (!userIdAsEids) { userIdAsEids = bid.userIdAsEids; } - const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd} = bid; + const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, rtd, ortb2Imp} = bid; bidsMap[bidId] = bid; const bidFloor = _getFloor(mediaTypes || {}, bid); if (rtd) { @@ -102,6 +101,15 @@ export const spec = { } }; + if (ortb2Imp && 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; @@ -272,12 +280,12 @@ export const spec = { }, getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { if (syncOptions.pixelEnabled) { - const syncsPerBidder = config.getConfig('userSync.syncsPerBidder'); let params = []; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - params.push(`gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); - } else { + params.push(`gdpr=${Number(gdprConsent.gdprApplies)}`); + } + if (typeof gdprConsent.consentString === 'string') { params.push(`gdpr_consent=${gdprConsent.consentString}`); } } @@ -285,17 +293,10 @@ export const spec = { params.push(`us_privacy=${uspConsent}`); } const stringParams = params.join('&'); - const syncs = [{ + return { type: 'image', - url: ADAPTER_SYNC_URL + (stringParams ? `?${stringParams}` : '') - }]; - if (syncsPerBidder > 1) { - syncs.push({ - type: 'image', - url: ADDITIONAL_SYNC_URL + (stringParams ? `&${stringParams}` : '') - }); - } - return syncs; + url: ADAPTER_SYNC_URL + stringParams + }; } } } diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index d9c9f84e050..fda6f47b2af 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -98,9 +98,16 @@ export const spec = { 'commons': commons }; const referer = bidderRequest.refererInfo.referer; + const canonicalUrl = getCanonicalUrl(); + if (referer) { + commons.referrer = referer; + } + if (canonicalUrl) { + commons.canonicalUrl = canonicalUrl; + } const hostname = parseUrl(referer).hostname; let domain = extractDomainFromHost(hostname); - const pageUrl = getCanonicalUrl() || referer; + const pageUrl = canonicalUrl || referer; const pubid = validBidRequests[0].params.publisherId; let reqUrl = `${URL}?pid=${pubid}&domain=${domain}`; diff --git a/modules/userId/eids.js b/modules/userId/eids.js index 87b6ecc1f1c..a290371b36d 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -257,7 +257,13 @@ const USER_IDS_CONFIG = { 'connectId': { source: 'yahoo.com', atype: 3 - } + }, + + // Adquery ID + 'qid': { + source: 'adquery.io', + atype: 1 + }, }; // this function will create an eid object for the given UserId sub-module diff --git a/modules/userId/index.js b/modules/userId/index.js index b0293a9c26a..42fff2cd16c 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -141,7 +141,7 @@ import { createEidsArray, buildEidPermissions } from './eids.js'; import { getCoreStorageManager } from '../../src/storageManager.js'; import { getPrebidInternal, isPlainObject, logError, isArray, cyrb53Hash, deepAccess, timestamp, delayExecution, logInfo, isFn, - logWarn, isEmptyStr, isNumber + logWarn, isEmptyStr, isNumber, isGptPubadsDefined } from '../../src/utils.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -184,6 +184,9 @@ export let syncDelay; /** @type {(number|undefined)} */ export let auctionDelay; +/** @type {(string|undefined)} */ +let ppidSource; + /** @param {Submodule[]} submodules */ export function setSubmoduleRegistry(submodules) { submoduleRegistry = submodules; @@ -557,6 +560,26 @@ export function requestBidsHook(fn, reqBidsConfigObj) { initializeSubmodulesAndExecuteCallbacks(function () { // 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); + }); + } + } else { + logWarn(`User ID - Googletag Publisher Provided ID for ${ppidSource} is not between 32 and 150 characters - ${ppidValue}`); + } + } + // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); }); @@ -817,6 +840,7 @@ export function attachIdSystem(submodule) { * @param {{getConfig:function}} config */ export function init(config) { + ppidSource = undefined; submodules = []; configRegistry = []; addedUserIdHook = false; @@ -839,9 +863,10 @@ export function init(config) { } // listen for config userSyncs to be set - config.getConfig(conf => { + config.getConfig('userSync', conf => { // Note: support for 'usersync' was dropped as part of Prebid.js 4.0 const userSync = conf.userSync; + ppidSource = userSync.ppid; if (userSync && userSync.userIds) { configRegistry = userSync.userIds; syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 095685aba3d..6364de8ea79 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -297,6 +297,14 @@ pbjs.setConfig({ type: 'html5', expires: 15 } + } + { + name: "qid", + storage: { + type: "html5", + name: "qid", + expires: 365 + } }], syncDelay: 5000 } diff --git a/modules/ventes.md b/modules/ventes.md new file mode 100644 index 00000000000..5f0f571ecd8 --- /dev/null +++ b/modules/ventes.md @@ -0,0 +1,71 @@ +--- +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 7a2b60d2ee2..a9de52a86ba 100644 --- a/modules/ventesBidAdapter.js +++ b/modules/ventesBidAdapter.js @@ -1,17 +1,24 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {convertCamelToUnderscore, isStr, isArray, isNumber, isPlainObject, replaceAuctionPrice} from '../src/utils.js'; +import { + BANNER, + NATIVE, + VIDEO +} from '../src/mediaTypes.js'; +import { + convertCamelToUnderscore, + isStr, + isArray, + isNumber, + isPlainObject, + replaceAuctionPrice +} from '../src/utils.js'; import find from 'core-js-pure/features/array/find.js'; -import includes from 'core-js-pure/features/array/includes.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; const BID_METHOD = 'POST'; const BIDDER_URL = 'http://13.234.201.146:8088/va/ad'; -const FIRST_PRICE = 1; -const NET_REVENUE = true; -const TTL = 10; -const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; -const DEVICE_PARAMS = ['ua', 'geo', 'dnt', 'lmt', 'ip', 'ipv6', 'devicetype']; -const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately + const DOMAIN_REGEX = new RegExp('//([^/]*)'); function groupBy(values, key) { @@ -26,7 +33,11 @@ function groupBy(values, key) { return Object .keys(groups) - .map(id => ({id, key, values: groups[id]})); + .map(id => ({ + id, + key, + values: groups[id] + })); } function validateMediaTypes(mediaTypes, allowedMediaTypes) { @@ -45,22 +56,22 @@ function isBanner(mediaTypes) { function validateBanner(banner) { return isPlainObject(banner) && - isArray(banner.sizes) && - (banner.sizes.length > 0) && - banner.sizes.every(validateMediaSizes); + isArray(banner.sizes) && + (banner.sizes.length > 0) && + banner.sizes.every(validateMediaSizes); } function validateMediaSizes(mediaSize) { return isArray(mediaSize) && - (mediaSize.length === 2) && - mediaSize.every(size => (isNumber(size) && size >= 0)); + (mediaSize.length === 2) && + mediaSize.every(size => (isNumber(size) && size >= 0)); } function hasUserInfo(bid) { return !!bid.params.user; } -function validateParameters(parameters, adUnit) { +function validateParameters(parameters) { if (!(parameters.placementId)) { return false; } @@ -101,8 +112,8 @@ function generateSiteFromAdUnitContext(bidRequests, adUnitContext) { function validateServerRequest(serverRequest) { return isPlainObject(serverRequest) && - isPlainObject(serverRequest.data) && - isArray(serverRequest.data.imp) + isPlainObject(serverRequest.data) && + isArray(serverRequest.data.imp) } function createServerRequestFromAdUnits(adUnits, bidRequestId, adUnitContext) { @@ -122,14 +133,15 @@ function generateBidRequestsFromAdUnits(bidRequests, bidRequestId, adUnitContext let userObj = {}; if (userObjBid) { Object.keys(userObjBid.params.user) - .filter(param => includes(USER_PARAMS, param)) .forEach((param) => { let uparam = convertCamelToUnderscore(param); if (param === 'segments' && isArray(userObjBid.params.user[param])) { let segs = []; userObjBid.params.user[param].forEach(val => { if (isNumber(val)) { - segs.push({'id': val}); + segs.push({ + 'id': val + }); } else if (isPlainObject(val)) { segs.push(val); } @@ -146,7 +158,6 @@ function generateBidRequestsFromAdUnits(bidRequests, bidRequestId, adUnitContext if (deviceObjBid && deviceObjBid.params && deviceObjBid.params.device) { deviceObj = {}; Object.keys(deviceObjBid.params.device) - .filter(param => includes(DEVICE_PARAMS, param)) .forEach(param => deviceObj[param] = deviceObjBid.params.device[param]); if (!deviceObjBid.hasOwnProperty('ua')) { deviceObj.ua = navigator.userAgent; @@ -159,37 +170,41 @@ function generateBidRequestsFromAdUnits(bidRequests, bidRequestId, adUnitContext deviceObj.ua = navigator.userAgent; deviceObj.language = navigator.language; } - const appDeviceObjBid = find(bidRequests, hasAppInfo); - let appIdObj; - if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { - Object.keys(appDeviceObjBid.params.app) - .filter(param => includes(APP_DEVICE_PARAMS, param)) - .forEach(param => appDeviceObjBid[param] = appDeviceObjBid.params.app[param]); - } const payload = {} payload.id = bidRequestId - payload.at = FIRST_PRICE + payload.at = 1 payload.cur = ['USD'] payload.imp = bidRequests.reduce(generateImpressionsFromAdUnit, []) - payload.site = generateSiteFromAdUnitContext(bidRequests, adUnitContext) - payload.device = deviceObj - if (appDeviceObjBid && payload.site != null) { + const appDeviceObjBid = find(bidRequests, hasAppInfo); + if (!appDeviceObjBid) { + payload.site = generateSiteFromAdUnitContext(bidRequests, adUnitContext) + } else { + let appIdObj; + if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { + appIdObj = {}; + Object.keys(appDeviceObjBid.params.app) + .forEach(param => appIdObj[param] = appDeviceObjBid.params.app[param]); + } payload.app = appIdObj; } + payload.device = deviceObj; payload.user = userObj - // payload.regs = getRegulationFromAdUnitContext(adUnitContext) - // payload.ext = generateBidRequestExtension() - return payload } function generateImpressionsFromAdUnit(acc, adUnit) { - const {bidId, mediaTypes, params} = adUnit; - const {placementId} = params; + const { + bidId, + mediaTypes, + params + } = adUnit; + const { + placementId + } = params; const pmp = {}; - if (placementId) pmp.deals = [{id: placementId}] + if (placementId) pmp.deals = [{ id: placementId }] const imps = Object .keys(mediaTypes) @@ -204,21 +219,40 @@ function generateImpressionsFromAdUnit(acc, adUnit) { } function generateBannerFromAdUnit(impId, data, params) { - const {position, placementId} = params; + const { + position, + placementId + } = params; const pos = position || 0; const pmp = {}; - const ext = {placementId}; - - if (placementId) pmp.deals = [{id: placementId}] + const ext = { + placementId + }; - return data.sizes.map(([w, h]) => ({id: `${impId}`, banner: {format: [{w, h}], w, h, pos}, pmp, ext, tagid: placementId})); + if (placementId) pmp.deals = [{ id: placementId }] + + return data.sizes.map(([w, h]) => ({ + id: `${impId}`, + banner: { + format: [{ + w, + h + }], + w, + h, + pos + }, + pmp, + ext, + tagid: placementId + })); } function validateServerResponse(serverResponse) { return isPlainObject(serverResponse) && - isPlainObject(serverResponse.body) && - isStr(serverResponse.body.cur) && - isArray(serverResponse.body.seatbid); + isPlainObject(serverResponse.body) && + isStr(serverResponse.body.cur) && + isArray(serverResponse.body.seatbid); } function seatBidsToAds(seatBid, bidResponse, serverRequest) { @@ -241,10 +275,8 @@ function validateBids(bid) { return true; } -const VAST_REGEXP = /VAST\s+version/; - function getMediaType(adm) { - const videoRegex = new RegExp(VAST_REGEXP); + const videoRegex = new RegExp(/VAST\s+version/); if (videoRegex.test(adm)) { return VIDEO; @@ -273,14 +305,16 @@ function generateAdFromBid(bid, bidResponse) { requestId: bid.impid, cpm: bid.price, currency: bidResponse.cur, - ttl: TTL, + ttl: 10, creativeId: bid.crid, mediaType: mediaType, - netRevenue: NET_REVENUE + netRevenue: true }; if (bid.adomain) { - base.meta = { advertiserDomains: bid.adomain }; + base.meta = { + advertiserDomains: bid.adomain + }; } const size = getSizeFromBid(bid); @@ -292,17 +326,21 @@ function generateAdFromBid(bid, bidResponse) { width: size.width, ad: creative.markup, adUrl: creative.markupUrl, - // vastXml: isVideo && !isStr(creative.markupUrl) ? creative.markup : null, - // vastUrl: isVideo && isStr(creative.markupUrl) ? creative.markupUrl : null, renderer: creative.renderer }; } function getSizeFromBid(bid) { if (isNumber(bid.w) && isNumber(bid.h)) { - return { width: bid.w, height: bid.h }; + return { + width: bid.w, + height: bid.h + }; } - return { width: null, height: null }; + return { + width: null, + height: null + }; } function getCreativeFromBid(bid) { @@ -333,12 +371,12 @@ const venavenBidderSpec = { const allowedBidderCodes = [this.code]; return isPlainObject(adUnit) && - allowedBidderCodes.indexOf(adUnit.bidder) !== -1 && - isStr(adUnit.adUnitCode) && - isStr(adUnit.bidderRequestId) && - isStr(adUnit.bidId) && - validateMediaTypes(adUnit.mediaTypes, this.supportedMediaTypes) && - validateParameters(adUnit.params, adUnit); + allowedBidderCodes.indexOf(adUnit.bidder) !== -1 && + isStr(adUnit.adUnitCode) && + isStr(adUnit.bidderRequestId) && + isStr(adUnit.bidId) && + validateMediaTypes(adUnit.mediaTypes, this.supportedMediaTypes) && + validateParameters(adUnit.params); }, buildRequests(bidRequests, bidderRequest) { if (!bidRequests) return null; @@ -367,4 +405,6 @@ const venavenBidderSpec = { registerBidder(venavenBidderSpec); -export {venavenBidderSpec as spec}; +export { + venavenBidderSpec as spec +}; diff --git a/modules/ventesBidAdapter.md b/modules/ventesBidAdapter.md index 479f6dd2898..c79ef080cd1 100644 --- a/modules/ventesBidAdapter.md +++ b/modules/ventesBidAdapter.md @@ -55,7 +55,6 @@ var adUnits = [ publisherId: '5cebea3c9eea646c7b623d5e', IABCategories: "['IAB1', 'IAB5']", device:{ - ip: '123.145.167.189', ifa:"AEBE52E7-03EE-455A-B3C4-E57283966239", }, app: { diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index e69386f264a..64145b2c6b4 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -1,4 +1,4 @@ -import { logError, deepAccess } from '../src/utils.js'; +import { logError, deepAccess, parseSizesInput } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -9,7 +9,17 @@ const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`; const BIDDER_CODE = 'vidoomy'; const GVLID = 380; -const COOKIE_SYNC_JSON = 'https://vpaid.vidoomy.com/sync/urls.json'; +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://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' +]; const isBidRequestValid = bid => { if (!bid.params) { @@ -36,14 +46,14 @@ const isBidRequestValid = bid => { }; const isBidResponseValid = bid => { - if (!bid.requestId || !bid.cpm || !bid.ttl || !bid.currency) { + if (!bid || !bid.requestId || !bid.cpm || !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); + return Boolean(bid.vastUrl || bid.vastXml); default: return false; } @@ -52,14 +62,15 @@ const isBidResponseValid = bid => { const buildRequests = (validBidRequests, bidderRequest) => { const serverRequests = validBidRequests.map(bid => { let adType = BANNER; - let w, h; + let sizes; if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { - [w, h] = bid.mediaTypes[BANNER].sizes[0]; + sizes = bid.mediaTypes[BANNER].sizes; adType = BANNER; } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { - [w, h] = bid.mediaTypes[VIDEO].playerSize; + sizes = bid.mediaTypes[VIDEO].playerSize; adType = VIDEO; } + const [w, h] = (parseSizesInput(sizes)[0] || '0x0').split('x'); const aElement = document.createElement('a'); aElement.href = (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) || top.location.href; @@ -67,35 +78,34 @@ const buildRequests = (validBidRequests, bidderRequest) => { const videoContext = deepAccess(bid, 'mediaTypes.video.context'); - const queryParams = []; - queryParams.push(['id', bid.params.id]); - queryParams.push(['adtype', adType]); - queryParams.push(['w', w]); - queryParams.push(['h', h]); - queryParams.push(['pos', parseInt(bid.params.position) || 1]); - queryParams.push(['ua', navigator.userAgent]); - queryParams.push(['l', navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '']); - queryParams.push(['dt', /Mobi/.test(navigator.userAgent) ? 2 : 1]); - queryParams.push(['pid', bid.params.pid]); - queryParams.push(['requestId', bid.bidId]); - queryParams.push(['d', getDomainWithoutSubdomain(hostname)]); - queryParams.push(['sp', encodeURIComponent(aElement.href)]); + const queryParams = { + id: bid.params.id, + adtype: adType, + auc: bid.adUnitCode, + w, + h, + pos: parseInt(bid.params.position) || 1, + ua: navigator.userAgent, + l: navigator.language && navigator.language.indexOf('-') !== -1 ? navigator.language.split('-')[0] : '', + dt: /Mobi/.test(navigator.userAgent) ? 2 : 1, + pid: bid.params.pid, + requestId: bid.bidId, + d: getDomainWithoutSubdomain(hostname), // 'vidoomy.com', + sp: encodeURIComponent(aElement.href), + usp: bidderRequest.uspConsent || '', + coppa: !!config.getConfig('coppa'), + videoContext: videoContext || '' + }; + if (bidderRequest.gdprConsent) { - queryParams.push(['gdpr', bidderRequest.gdprConsent.gdprApplies]); - queryParams.push(['gdprcs', bidderRequest.gdprConsent.consentString]); + queryParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + queryParams.gdprcs = bidderRequest.gdprConsent.consentString; } - queryParams.push(['usp', bidderRequest.uspConsent || '']); - queryParams.push(['coppa', !!config.getConfig('coppa')]); - - const rawQueryParams = queryParams.map(qp => qp.join('=')).join('&'); - cookieSync(bidderRequest) - - const url = `${ENDPOINT}?${rawQueryParams}`; return { method: 'GET', - url: url, - data: {videoContext} + url: ENDPOINT, + data: queryParams } }); return serverRequests; @@ -117,8 +127,9 @@ const render = (bid) => { const interpretResponse = (serverResponse, bidRequest) => { try { let responseBody = serverResponse.body; + if (!responseBody) return; if (responseBody.mediaType === 'video') { - responseBody.ad = responseBody.vastUrl; + responseBody.ad = responseBody.vastUrl || responseBody.vastXml; const videoContext = bidRequest.data.videoContext; if (videoContext === OUTSTREAM) { @@ -134,13 +145,12 @@ const interpretResponse = (serverResponse, bidRequest) => { responseBody.renderer = renderer; } catch (e) { - responseBody.ad = responseBody.vastUrl; + responseBody.ad = responseBody.vastUrl || responseBody.vastXml; logError(BIDDER_CODE + ': error while installing renderer to show outstream ad'); } } } const bid = { - vastUrl: responseBody.vastUrl, ad: responseBody.ad, renderer: responseBody.renderer, mediaType: responseBody.mediaType, @@ -169,6 +179,11 @@ const interpretResponse = (serverResponse, bidRequest) => { secondaryCatIds: responseBody.meta.secondaryCatIds } }; + if (responseBody.vastUrl) { + bid.vastUrl = responseBody.vastUrl; + } else if (responseBody.vastXml) { + bid.vastXml = responseBody.vastXml; + } const bids = []; @@ -185,6 +200,21 @@ const interpretResponse = (serverResponse, bidRequest) => { } }; +function getUserSyncs (syncOptions, responses, gdprConsent, uspConsent) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + const pixelType = syncOptions.pixelEnabled ? 'image' : 'iframe'; + const urls = deepAccess(responses, '0.body.pixels') || COOKIE_SYNC_FALLBACK_URLS; + + return [].concat(urls).map(url => ({ + type: pixelType, + url: url + .replace('{{GDPR}}', gdprConsent ? (gdprConsent.gdprApplies ? '1' : '0') : '0') + .replace('{{GDPR_CONSENT}}', gdprConsent ? encodeURIComponent(gdprConsent.consentString) : '') + .replace('{{USP_CONSENT}}', uspConsent ? encodeURIComponent(uspConsent) : '') + })); + } +}; + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], @@ -192,84 +222,11 @@ export const spec = { buildRequests, interpretResponse, gvlid: GVLID, + getUserSyncs, }; registerBidder(spec); -let cookieSynced = false; -function cookieSync(bidderRequest) { - if (cookieSynced) return; - const xhr = new XMLHttpRequest(); - xhr.open('GET', COOKIE_SYNC_JSON) - xhr.addEventListener('load', function () { - const macro = Macro({ - gpdr: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : '0', - gpdr_consent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : '', - }); - JSON.parse(this.responseText).filter(Boolean).forEach(url => { - firePixel(macro.replace(url)) - }) - }) - xhr.send() - cookieSynced = true; -} - -function firePixel(url) { - const img = document.createElement('img'); - img.width = 1; - img.height = 1; - img.src = url; - document.body.appendChild(img); - setTimeout(() => { - img.remove(); - }, 10000) -} - -function normalizeKey (x) { - return x.replace(/_/g, '').toLowerCase(); -} - -function Macro (obj) { - const macros = {}; - for (const key in obj) { - macros[normalizeKey(key)] = obj[key]; - } - - const set = (key, value) => { - macros[normalizeKey(key)] = typeof value === 'function' ? value : String(value); - }; - - return { - set, - setAll (obj) { - for (const key in obj) { - macros[normalizeKey(key)] = set(obj[key]); - } - }, - replace (string, extraMacros = {}) { - const allMacros = { - ...macros, - ...extraMacros, - }; - const regexes = [ - /{{\s*([a-zA-Z0-9_]+)\s*}}/g, - /\$\$\s*([a-zA-Z0-9_]+)\s*\$\$/g, - /\[\s*([a-zA-Z0-9_]+)\s*\]/g, - /\{\s*([a-zA-Z0-9_]+)\s*\}/g, - ]; - regexes.forEach(regex => { - string = string.replace(regex, (str, x) => { - x = normalizeKey(x); - let value = allMacros[x]; - value = typeof value === 'function' ? value(allMacros) : value; - return !value && value !== 0 ? '' : value; - }); - }); - return string; - }, - }; -} - function getDomainWithoutSubdomain (hostname) { const parts = hostname.split('.'); const newParts = []; diff --git a/modules/viewability.js b/modules/viewability.js new file mode 100644 index 00000000000..b12c53b7f59 --- /dev/null +++ b/modules/viewability.js @@ -0,0 +1,177 @@ +import { logWarn, logInfo, isStr, isFn, triggerPixel, insertHtmlIntoIframe } from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import find from 'core-js-pure/features/array/find.js'; + +export const MODULE_NAME = 'viewability'; + +export function init() { + (getGlobal()).viewability = { + startMeasurement: startMeasurement, + stopMeasurement: stopMeasurement, + }; + + listenMessagesFromCreative(); +} + +const observers = {}; + +function isValid(vid, element, tracker, criteria) { + if (!element) { + logWarn(`${MODULE_NAME}: no html element provided`); + return false; + } + + let validTracker = tracker && + ((tracker.method === 'img' && isStr(tracker.value)) || + (tracker.method === 'js' && isStr(tracker.value)) || + (tracker.method === 'callback' && isFn(tracker.value))); + + if (!validTracker) { + logWarn(`${MODULE_NAME}: invalid tracker`, tracker); + return false; + } + + if (!criteria || !criteria.inViewThreshold || !criteria.timeInView) { + logWarn(`${MODULE_NAME}: missing criteria`, criteria); + return false; + } + + if (!vid || observers[vid]) { + logWarn(`${MODULE_NAME}: must provide an unregistered vid`, vid); + return false; + } + + return true; +} + +function stopObserving(observer, vid, element) { + observer.unobserve(element); + observers[vid].done = true; +} + +function fireViewabilityTracker(element, tracker) { + switch (tracker.method) { + case 'img': + triggerPixel(tracker.value, () => { + logInfo(`${MODULE_NAME}: viewability pixel fired`, tracker.value); + }); + break; + case 'js': + insertHtmlIntoIframe(``); + break; + case 'callback': + tracker.value(element); + break; + } +} + +function viewabilityCriteriaMet(observer, vid, element, tracker) { + stopObserving(observer, vid, element); + fireViewabilityTracker(element, tracker); +} + +/** + * Start measuring viewability of an element + * @typedef {{ method: string='img','js','callback', value: string|function }} ViewabilityTracker { method: 'img', value: 'http://my.tracker/123' } + * @typedef {{ inViewThreshold: number, timeInView: number }} ViewabilityCriteria { inViewThreshold: 0.5, timeInView: 1000 } + * @param {string} vid unique viewability identifier + * @param {HTMLElement} element + * @param {ViewabilityTracker} tracker + * @param {ViewabilityCriteria} criteria + */ +export function startMeasurement(vid, element, tracker, criteria) { + if (!isValid(vid, element, tracker, criteria)) { + return; + } + + const options = { + root: null, + rootMargin: '0px', + threshold: criteria.inViewThreshold, + }; + + let observer; + let viewable = false; + let stateChange = (entries) => { + viewable = entries[0].isIntersecting; + + if (viewable) { + observers[vid].timeoutId = window.setTimeout(() => { + viewabilityCriteriaMet(observer, vid, element, tracker); + }, criteria.timeInView); + } else if (observers[vid].timeoutId) { + window.clearTimeout(observers[vid].timeoutId); + } + }; + + observer = new IntersectionObserver(stateChange, options); + observers[vid] = { + observer: observer, + element: element, + timeoutId: null, + done: false, + }; + + observer.observe(element); + + logInfo(`${MODULE_NAME}: startMeasurement called with:`, arguments); +} + +/** + * Stop measuring viewability of an element + * @param {string} vid unique viewability identifier + */ +export function stopMeasurement(vid) { + if (!vid || !observers[vid]) { + logWarn(`${MODULE_NAME}: must provide a registered vid`, vid); + return; + } + + observers[vid].observer.unobserve(observers[vid].element); + if (observers[vid].timeoutId) { + window.clearTimeout(observers[vid].timeoutId); + } + + // allow the observer under this vid to be created again + if (!observers[vid].done) { + delete observers[vid]; + } +} + +function listenMessagesFromCreative() { + window.addEventListener('message', receiveMessage, false); +} + +/** + * Recieve messages from creatives + * @param {MessageEvent} evt + */ +export function receiveMessage(evt) { + var key = evt.message ? 'message' : 'data'; + var data = {}; + try { + data = JSON.parse(evt[key]); + } catch (e) { + return; + } + + if (!data || data.message !== 'Prebid Viewability') { + return; + } + + switch (data.action) { + case 'startMeasurement': + let element = data.elementId && document.getElementById(data.elementId); + if (!element) { + element = find(document.getElementsByTagName('IFRAME'), iframe => (iframe.contentWindow || iframe.contentDocument.defaultView) == evt.source); + } + + startMeasurement(data.vid, element, data.tracker, data.criteria); + break; + case 'stopMeasurement': + stopMeasurement(data.vid); + break; + } +} + +init(); diff --git a/modules/viewability.md b/modules/viewability.md new file mode 100644 index 00000000000..df93b5c40db --- /dev/null +++ b/modules/viewability.md @@ -0,0 +1,87 @@ +# Overview + +Module Name: Viewability + +Purpose: Track when a given HTML element becomes viewable + +Maintainer: atrajkovic@magnite.com + +# Configuration + +Module does not need any configuration, as long as you include it in your PBJS bundle. +Viewability module has only two functions `startMeasurement` and `stopMeasurement` which can be used to enable more complex viewability measurements. Since it allows tracking from within creative (possibly inside a safe frame) this module registers a message listener, for messages with a format that is described bellow. + +## `startMeasurement` + +| startMeasurement Arg Object | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| vid | Required | String | Unique viewability identifier, used to reference particular observer | `"ae0f9"` | +| element | Required | HTMLElement | Reference to an HTML element that needs to be tracked | `document.getElementById('test_div')` | +| tracker | Required | ViewabilityTracker | How viewaility event is communicated back to the parties of interest | `{ method: 'img', value: 'http://my.tracker/123' }` | +| criteria | Required | ViewabilityCriteria| Defines custom viewability criteria using the threshold and duration provided | `{ inViewThreshold: 0.5, timeInView: 1000 }` | + +| ViewabilityTracker | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| method | Required | String | Type of method for Tracker | `'img' OR 'js' OR 'callback'` | +| value | Required | String | URL string for 'img' and 'js' Trackers, or a function for 'callback' Tracker | `'http://my.tracker/123'` | + +| ViewabilityCriteria | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| inViewThreshold | Required | Number | Represents a percentage threshold for the Element to be registered as in view | `0.5` | +| timeInView | Required | Number | Number of milliseconds that a given element needs to be in view continuously, above the threshold | `1000` | + +## Please Note: +- `vid` allows for multiple trackers, with different criteria to be registered for a given HTML element, independently. It's not autogenerated by `startMeasurement()`, it needs to be provided by the caller so that it doesn't have to be posted back to the source iframe (in case viewability is started from within the creative). +- In case of 'callback' method, HTML element is being passed back to the callback function. +- When a tracker needs to be started, without direct access to pbjs, postMessage mechanism can be used to invoke `startMeasurement`, with a following payload: `vid`, `tracker` and `criteria` as described above, but also with `message: 'Prebid Viewability'` and `action: 'startMeasurement'`. Optionally payload can provide `elementId`, if available at that time (for ad servers where name of the iframe is known, or adservers that render outside an iframe). If `elementId` is not provided, viewability module will try to find the iframe that corresponds to the message source. + + +## `stopMeasurement` + +| stopMeasurement Arg Object | Scope | Type | Description | Example | +| --------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | --------- | +| vid | Required | String | Unique viewability identifier, referencing an already started viewability tracker. | `"ae0f9"` | + +## Please Note: +- When a tracker needs to be stopped, without direct access to pbjs, postMessage mechanism can be used here as well. To invoke `stopMeasurement`, you provide the payload with `vid`, `message: 'Prebid Viewability'` and `action: 'stopMeasurement`. Check the example bellow. + +# Examples + +## Example of starting a viewability measurement, when you have direct access to pbjs +``` +pbjs.viewability.startMeasurement( + 'ae0f9', + document.getElementById('test_div'), + { method: 'img', value: 'http://my.tracker/123' }, + { inViewThreshold: 0.5, timeInView: 1000 } +); +``` + +## Example of starting a viewability measurement from within a rendered creative +``` +let viewabilityRecord = { + vid: 'ae0f9', + tracker: { method: 'img', value: 'http://my.tracker/123'}, + criteria: { inViewThreshold: 0.5, timeInView: 1000 }, + message: 'Prebid Viewability', + action: 'startMeasurement' +} + +window.parent.postMessage(JSON.stringify(viewabilityRecord), '*'); +``` + +## Example of stopping the viewability measurement, when you have direct access to pbjs +``` +pbjs.viewability.stopMeasurement('ae0f9'); +``` + +## Example of stopping the viewability measurement from within a rendered creative +``` +let viewabilityRecord = { + vid: 'ae0f9', + message: 'Prebid Viewability', + action: 'stopMeasurement' +} + +window.parent.postMessage(JSON.stringify(viewabilityRecord), '*'); +``` diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index 1d80ea79e99..af8672ea233 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -1,4 +1,4 @@ -import { triggerPixel, parseSizesInput, deepAccess, logError } from '../src/utils.js'; +import { triggerPixel, parseSizesInput, deepAccess, logError, getGptSlotInfoForAdUnitCode } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; @@ -203,7 +203,7 @@ export const spec = { }, onTimeout: function(timeoutData) { // Call '/track/bid_timeout' with timeout data - triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '?data=' + JSON.stringify(timeoutData)); + triggerPixel(buildUrl(TRACK_TIMEOUT_PATH) + '//' + JSON.stringify(timeoutData)); } }; @@ -241,7 +241,7 @@ function makeVideo(videoParams = {}) { } function buildImpObject(bid) { - const { params: { uid }, bidId, mediaTypes, sizes } = bid; + const { params: { uid }, bidId, mediaTypes, sizes, adUnitCode } = bid; const video = mediaTypes && _isVideoBid(bid) && _isValidVideoBid(bid) && makeVideo(mediaTypes.video); const banner = makeBanner((mediaTypes && mediaTypes.banner) || (!video && { sizes })); const impObject = { @@ -253,6 +253,10 @@ function buildImpObject(bid) { } }; + if (impObject.banner) { + impObject.ext.bidder.adslotExists = _isAdSlotExists(adUnitCode); + } + if (impObject.ext.bidder.uid && (impObject.banner || impObject.video)) { return impObject; } @@ -355,4 +359,17 @@ function _isValidVideoBid(bid, logErrors = false) { return result; } +function _isAdSlotExists(adUnitCode) { + if (document.getElementById(adUnitCode)) { + return true; + } + + const gptAdSlot = getGptSlotInfoForAdUnitCode(adUnitCode); + if (gptAdSlot && gptAdSlot.divId && document.getElementById(gptAdSlot.divId)) { + return true; + } + + return false; +} + registerBidder(spec); diff --git a/modules/visxBidAdapter.md b/modules/visxBidAdapter.md index 9578f7cc4a7..34ebe9bb937 100644 --- a/modules/visxBidAdapter.md +++ b/modules/visxBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: YOC VIS.X Bidder Adapter Module Type: Bidder Adapter -Maintainer: service@yoc.com +Maintainer: supply.partners@yoc.com ``` # Description @@ -47,16 +47,14 @@ var adUnits = [ } ] }, - // YOC In-stream adUnit + // In-stream video adUnit { code: 'instream-test-div', mediaTypes: { video: { context: 'instream', - playerSize: [400, 300], - mimes: ['video/mp4'], - protocols: [3, 6] - }, + playerSize: [400, 300] + } }, bids: [ { diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 01086ad129f..70f258e1698 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -2,90 +2,326 @@ * This module adds Weborama provider to the real time data module * The {@link module:modules/realTimeData} module is required * The module will fetch contextual data (page-centric) from Weborama server + * and may access user-centric data from local storage * @module modules/weboramaRtdProvider * @requires module:modules/realTimeData */ /** -* @typedef {Object} ModuleParams -* @property {WeboCtxConf} weboCtxConf -*/ + * @typedef {Object} ModuleParams + * @property {WeboCtxConf} weboCtxConf + * @property {WeboUserDataConf} weboUserDataConf + */ /** -* @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} setTargeting if true will set the GAM targeting -* @property {?object} defaultProfile to be used if the profile is not found -*/ - -import { deepSetValue, logError, tryAppendQueryString, logMessage } from '../src/utils.js'; -import {submodule} from '../src/hook.js'; -import {ajax} from '../src/ajax.js'; -import {config} from '../src/config.js'; + * @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 true) + * @property {?Boolean} sendToBidders if true, will send the contextual profile to all bidders (default true) + * @property {?object} defaultProfile to be used if the profile is not found + */ + +/** + * @typedef {Object} WeboUserDataConf + * @property {?string} localStorageProfileKey can be used to customize the local storage key (default is 'webo_wam2gam_entry') + * @property {?Boolean} setPrebidTargeting if true will set the GAM targeting (default true) + * @property {?Boolean} sendToBidders if true, will send the contextual profile to all bidders (default true) + * @property {?object} defaultProfile to be used if the profile is not found + */ + +import { + getGlobal +} from '../src/prebidGlobal.js'; +import { + deepSetValue, + deepAccess, + isEmpty, + mergeDeep, + logError, + tryAppendQueryString, + logMessage +} from '../src/utils.js'; +import { + submodule +} from '../src/hook.js'; +import { + ajax +} from '../src/ajax.js'; +import { + getStorageManager +} from '../src/storageManager.js'; + +const adapterManager = require('../src/adapterManager.js').default; /** @type {string} */ const MODULE_NAME = 'realTimeData'; /** @type {string} */ const SUBMODULE_NAME = 'weborama'; /** @type {string} */ -const WEBO_CTX = 'webo_ctx'; +export const DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY = 'webo_wam2gam_entry'; /** @type {string} */ -const WEBO_DS = 'webo_ds'; +const LOCAL_STORAGE_USER_TARGETING_SECTION = 'targeting'; +/** @type {number} */ +const GVLID = 284; +/** @type {object} */ +export const storage = getStorageManager(GVLID, SUBMODULE_NAME); /** @type {null|Object} */ -let _bigseaContextualProfile = null; +let _weboContextualProfile = null; -/** function that provides ad server targeting data to RTD-core -* @param {Array} adUnitsCodes -* @param {Object} moduleConfig -* @returns {Object} target data +/** @type {Boolean} */ +let _weboCtxInitialized = false; + +/** @type {null|Object} */ +let _weboUserDataUserProfile = null; + +/** @type {Boolean} */ +let _weboUserDataInitialized = false; + +/** Initialize module + * @param {object} moduleConfig + * @return {Boolean} true if module was initialized with success */ -function getTargetingData(adUnitsCodes, moduleConfig) { +function init(moduleConfig) { moduleConfig = moduleConfig || {}; const moduleParams = moduleConfig.params || {}; const weboCtxConf = moduleParams.weboCtxConf || {}; - const defaultContextualProfiles = weboCtxConf.defaultProfile || {} - const profile = _bigseaContextualProfile || defaultContextualProfiles; + const weboUserDataConf = moduleParams.weboUserDataConf; - if (weboCtxConf.setOrtb2) { - const ortb2 = config.getConfig('ortb2') || {}; - if (profile[WEBO_CTX]) { - deepSetValue(ortb2, 'site.ext.data.webo_ctx', profile[WEBO_CTX]); - } - if (profile[WEBO_DS]) { - deepSetValue(ortb2, 'site.ext.data.webo_ds', profile[WEBO_DS]); - } - config.setConfig({ortb2: ortb2}); - } + _weboCtxInitialized = initWeboCtx(weboCtxConf); + _weboUserDataInitialized = initWeboUserData(weboUserDataConf); - if (weboCtxConf.setTargeting === false) { - return {}; + return _weboCtxInitialized || _weboUserDataInitialized; +} + +/** Initialize contextual sub module + * @param {WeboCtxConf} weboCtxConf + * @return {Boolean} true if sub module was initialized with success + */ +function initWeboCtx(weboCtxConf) { + _weboCtxInitialized = false; + _weboContextualProfile = null; + + if (!weboCtxConf.token) { + logError('missing param "token" for weborama contextual sub module initialization'); + return false; } + return true; +} + +/** Initialize weboUserData sub module + * @param {WeboUserDataConf} weboUserDataConf + * @return {Boolean} true if sub module was initialized with success + */ +function initWeboUserData(weboUserDataConf) { + _weboUserDataInitialized = false; + _weboUserDataUserProfile = null; + + return !!weboUserDataConf; +} + +/** function that provides ad server targeting data to RTD-core + * @param {Array} adUnitsCodes + * @param {Object} moduleConfig + * @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 !== false; + const weboUserDataConfTargeting = weboUserDataConf.setPrebidTargeting !== false; + try { - const formattedProfile = profile; - const r = adUnitsCodes.reduce((rp, adUnitCode) => { + const profile = getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting); + + if (isEmpty(profile)) { + return {}; + } + + const td = adUnitsCodes.reduce((data, adUnitCode) => { if (adUnitCode) { - rp[adUnitCode] = formattedProfile; + data[adUnitCode] = profile; } - return rp; + return data; }, {}); - return r; + + return td; } catch (e) { logError('unable to format weborama rtd targeting data', e); return {}; } } +/** function that provides complete profile formatted to be used + * @param {ModuleParams} moduleParams + * @param {Boolean} weboCtxConfTargeting + * @param {Boolean} weboUserDataConfTargeting + * @returns {Object} complete profile + */ +function getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting) { + const profile = {}; + + if (weboCtxConfTargeting) { + const contextualProfile = getContextualProfile(moduleParams.weboCtxConf || {}); + mergeDeep(profile, contextualProfile); + } + + if (weboUserDataConfTargeting) { + const weboUserDataProfile = getWeboUserDataProfile(moduleParams.weboUserDataConf || {}); + mergeDeep(profile, weboUserDataProfile); + } + + return profile; +} + +/** return contextual profile + * @param {WeboCtxConf} weboCtxConf + * @returns {Object} contextual profile + */ +function getContextualProfile(weboCtxConf) { + const defaultContextualProfile = weboCtxConf.defaultProfile || {}; + return _weboContextualProfile || defaultContextualProfile; +} + +/** return weboUserData profile + * @param {WeboUserDataConf} weboUserDataConf + * @returns {Object} weboUserData profile + */ +function getWeboUserDataProfile(weboUserDataConf) { + const weboUserDataDefaultUserProfile = weboUserDataConf.defaultProfile || {}; + + if (storage.localStorageIsEnabled() && !_weboUserDataUserProfile) { + const localStorageProfileKey = weboUserDataConf.localStorageProfileKey || DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY; + + 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]; + } + } + } + + return _weboUserDataUserProfile || weboUserDataDefaultUserProfile; +} + +/** function that will allow RTD sub-modules to modify the AdUnit object for each auction + * @param {Object} reqBidsConfigObj + * @param {doneCallback} onDone + * @param {Object} moduleConfig + * @returns {void} + */ +export function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig) { + moduleConfig = moduleConfig || {}; + const moduleParams = moduleConfig.params || {}; + const weboCtxConf = moduleParams.weboCtxConf || {}; + + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + + if (!_weboCtxInitialized) { + handleBidRequestData(adUnits, moduleParams); + + onDone(); + + return; + } + + fetchContextualProfile(weboCtxConf, (data) => { + logMessage('fetchContextualProfile on getBidRequestData is done'); + + setWeboContextualProfile(data); + }, () => { + handleBidRequestData(adUnits, moduleParams); + + onDone(); + }); +} + +/** function that handles bid request data + * @param {Object[]} adUnits + * @param {ModuleParams} moduleParams + * @returns {void} + */ + +function handleBidRequestData(adUnits, moduleParams) { + const weboCtxConf = moduleParams.weboCtxConf || {}; + const weboUserDataConf = moduleParams.weboUserDataConf || {}; + const weboCtxConfTargeting = weboCtxConf.sendToBidders !== false; + const weboUserDataConfTargeting = weboUserDataConf.sendToBidders !== false; + const profile = getCompleteProfile(moduleParams, weboCtxConfTargeting, weboUserDataConfTargeting); + + if (isEmpty(profile)) { + return; + } + + adUnits.forEach(adUnit => { + if (adUnit.hasOwnProperty('bids')) { + adUnit.bids.forEach(bid => handleBid(adUnit, profile, bid)); + } + }); +} + +/** @type {string} */ +const SMARTADSERVER = 'smartadserver'; + +/** @type {Object} */ +const bidderAliasRegistry = adapterManager.aliasRegistry || {}; + +/** handle individual bid + * @param {Object} adUnit + * @param {Object} profile + * @param {Object} bid + * @returns {void} + */ +function handleBid(adUnit, profile, bid) { + const bidder = bidderAliasRegistry[bid.bidder] || bid.bidder; + + logMessage('handle bidder', bidder, bid); + + switch (bidder) { + case SMARTADSERVER: + handleSmartadserverBid(adUnit, profile, bid); + + break; + } +} + +/** handle smartadserver bid + * @param {Object} adUnit + * @param {Object} profile + * @param {Object} bid + * @returns {void} + */ +function handleSmartadserverBid(adUnit, profile, bid) { + const target = []; + + if (deepAccess(bid, 'params.target')) { + target.push(bid.params.target.split(';')); + } + + Object.keys(profile).forEach(key => { + profile[key].forEach(value => { + const keyword = `${key}=${value}`; + if (target.indexOf(keyword) === -1) { + target.push(keyword); + } + }); + }); + + deepSetValue(bid, 'params.target', target.join(';')); +} + /** set bigsea contextual profile on module state - * if the profile is empty, will store the default profile * @param {null|Object} data * @returns {void} */ -export function setBigseaContextualProfile(data) { +export function setWeboContextualProfile(data) { if (data && Object.keys(data).length > 0) { - _bigseaContextualProfile = data; + _weboContextualProfile = data; } } @@ -96,9 +332,9 @@ export function setBigseaContextualProfile(data) { */ /** onDone callback type - * @callback doneCallback - * @returns {void} - */ + * @callback doneCallback + * @returns {void} + */ /** Fetch Bigsea Contextual Profile * @param {WeboCtxConf} weboCtxConf @@ -117,7 +353,7 @@ function fetchContextualProfile(weboCtxConf, onSuccess, onDone) { const url = 'https://ctx.weborama.com/api/profile?' + queryString; ajax(url, { - success: function (response, req) { + success: function(response, req) { if (req.status === 200) { try { const data = JSON.parse(response); @@ -126,49 +362,28 @@ function fetchContextualProfile(weboCtxConf, onSuccess, onDone) { } catch (e) { onDone(); logError('unable to parse weborama data', e); + throw e; } } else if (req.status === 204) { onDone(); } }, - error: function () { + error: function() { onDone(); logError('unable to get weborama data'); } }, - null, - { + null, { method: 'GET', withCredentials: false, }); } -/** Initialize module - * @param {object} moduleConfig - * @return {boolean} true if module was initialized with success - */ -function init(moduleConfig) { - _bigseaContextualProfile = null; - - moduleConfig = moduleConfig || {}; - const moduleParams = moduleConfig.params || {}; - const weboCtxConf = moduleParams.weboCtxConf || {}; - - if (weboCtxConf.token) { - fetchContextualProfile(weboCtxConf, setBigseaContextualProfile, - () => logMessage('fetchContextualProfile on init is done')); - } else { - logError('missing param "token" for weborama rtd module initialization'); - return false; - } - - return true; -} - export const weboramaSubmodule = { name: SUBMODULE_NAME, init: init, getTargetingData: getTargetingData, + getBidRequestData: getBidRequestData, }; submodule(MODULE_NAME, weboramaSubmodule); diff --git a/modules/weboramaRtdProvider.md b/modules/weboramaRtdProvider.md index e7b9b96d668..06e5b4fb43b 100644 --- a/modules/weboramaRtdProvider.md +++ b/modules/weboramaRtdProvider.md @@ -10,8 +10,6 @@ Maintainer: prebid-support@weborama.com 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. -ORTB2 compliant and FPD support for Prebid versions < 4.29 - Contact prebid-support@weborama.com for information. ### Publisher Usage @@ -32,17 +30,31 @@ pbjs.setConfig( name: "weborama", waitForIt: true, params: { - weboCtxConf: { - setTargeting: true, - token: "<>", - targetURL: "..." // default is document.URL + weboCtxConf: { // contextual configuration + token: "<>", // mandatory + targetURL: "...", // default is document.URL + setPrebidTargeting: true, // default + sendToBidders: true, // default + defaultProfile: { // optional, default is none + webo_ctx: ['foo'], + webo_ds: ['bar'] + } + }, + weboUserDataConf: { // user-centric configuration + setPrebidTargeting: true, // default + sendToBidders: true, // default + defaultProfile: { // optional, default is none + webo_cs: ['baz'], + webo_audiences: ['bam'] + }, + localStorageProfileKey: 'webo_wam2gam_entry' // default } } } ] } ... -} +); ``` ### Parameter Descriptions for the Weborama Configuration Section @@ -55,15 +67,20 @@ pbjs.setConfig( | params.weboCtxConf | Object | Weborama Contextual Configuration | Optional | | params.weboCtxConf.token | String | Security Token provided by Weborama, unique per client | Mandatory | | params.weboCtxConf.targetURL | String | Url to be profiled in the contextual api | Optional. Defaults to `document.URL` | +| params.weboCtxConf.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 *true*.| +| params.weboCtxConf.sendToBidders|Boolean|If true, will send the contextual profile to all bidders (only smartadserver is supported now)| Optional. Default is *true*.| | params.weboCtxConf.defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | -| params.weboCtxConf.setTargeting|Boolean|If true, will use the contextual profile to set the gam targeting of all adunits managed by prebid.js| Optional. Default is *true*.| -| params.weboCtxConf.setOrtb2|Boolean|If true, will use the contextual profile to set the ortb2 configuration on `site.ext.data`| Optional. Default is *false*.| +| params.weboUserDataConf | Object | WeboUserData Configuration | Optional | +| params.weboUserDataConf.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 *true*.| +| params.weboUserDataConf.sendToBidders|Boolean|If true, will send the contextual profile to all bidders (only smartadserver is supported now)| Optional. Default is *true*.| +| params.weboUserDataConf.defaultProfile | Object | default value of the profile to be used when there are no response from contextual api (such as timeout)| Optional. Default is `{}` | +| params.weboUserDataConf.localStorageProfileKey| String | can be used to customize the local storage key | Optional | ### Testing To view an example of available segments returned by Weborama's backends: -`gulp serve --modules=rtdModule,weboramaRtdProvider,appnexusBidAdapter` +`gulp serve --modules=rtdModule,weboramaRtdProvider,smartadserverBidAdapter` and then point your browser at: diff --git a/modules/welectBidAdapter.js b/modules/welectBidAdapter.js new file mode 100644 index 00000000000..d88a3f4c3e2 --- /dev/null +++ b/modules/welectBidAdapter.js @@ -0,0 +1,106 @@ +import { deepAccess } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'welect'; +const DEFAULT_DOMAIN = 'www.welect.de'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['wlt'], + gvlid: 282, + supportedMediaTypes: ['video'], + + // short code + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return ( + deepAccess(bid, 'mediaTypes.video.context') === 'instream' && + !!bid.params.placementId + ); + }, + /** + * 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) { + return validBidRequests.map((bidRequest) => { + let rawSizes = + deepAccess(bidRequest, 'mediaTypes.video.playerSize') || + bidRequest.sizes; + let size = rawSizes[0]; + + let domain = bidRequest.params.domain || DEFAULT_DOMAIN; + + let url = `https://${domain}/api/v2/preflight/${bidRequest.params.placementId}`; + + let gdprConsent = null; + + if (bidRequest && bidRequest.gdprConsent) { + gdprConsent = { + gdpr_consent: { + gdprApplies: bidRequest.gdprConsent.gdprApplies, + tcString: bidRequest.gdprConsent.gdprConsent, + }, + }; + } + + const data = { + width: size[0], + height: size[1], + bid_id: bidRequest.bidId, + ...gdprConsent, + }; + + return { + method: 'POST', + url: url, + data: data, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + }, + }; + }); + }, + /** + * 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 responseBody = serverResponse.body; + + if (typeof responseBody !== 'object' || responseBody.available !== true) { + return []; + } + + const bidResponses = []; + const bidResponse = { + requestId: responseBody.bidResponse.requestId, + cpm: responseBody.bidResponse.cpm, + width: responseBody.bidResponse.width, + height: responseBody.bidResponse.height, + creativeId: responseBody.bidResponse.creativeId, + currency: responseBody.bidResponse.currency, + netRevenue: responseBody.bidResponse.netRevenue, + ttl: responseBody.bidResponse.ttl, + ad: responseBody.bidResponse.ad, + vastUrl: responseBody.bidResponse.vastUrl, + meta: { + advertiserDomains: responseBody.bidResponse.meta.advertiserDomains + } + }; + bidResponses.push(bidResponse); + return bidResponses; + }, +}; +registerBidder(spec); diff --git a/modules/yahoosspBidAdapter.js b/modules/yahoosspBidAdapter.js index 101cb0ca9e3..73a991ec79a 100644 --- a/modules/yahoosspBidAdapter.js +++ b/modules/yahoosspBidAdapter.js @@ -6,7 +6,7 @@ import { Renderer } from '../src/Renderer.js'; const INTEGRATION_METHOD = 'prebid.js'; const BIDDER_CODE = 'yahoossp'; -const ADAPTER_VERSION = '1.0.1'; +const ADAPTER_VERSION = '1.0.2'; const PREBID_VERSION = '$prebid.version$'; const DEFAULT_BID_TTL = 300; const TEST_MODE_DCN = '8a969516017a7a396ec539d97f540011'; @@ -359,6 +359,10 @@ function appendImpObject(bid, openRtbObject) { impObject.ext.data = bid.ortb2Imp.ext.data; }; + if (deepAccess(bid, 'ortb2Imp.instl') && isNumber(bid.ortb2Imp.instl) && (bid.ortb2Imp.instl === 1)) { + impObject.instl = bid.ortb2Imp.instl; + }; + if (getPubIdMode(bid) === false) { impObject.tagid = bid.params.pos; impObject.ext.pos = bid.params.pos; diff --git a/modules/yahoosspBidAdapter.md b/modules/yahoosspBidAdapter.md index 5433e1fe3c9..7fb7a307192 100644 --- a/modules/yahoosspBidAdapter.md +++ b/modules/yahoosspBidAdapter.md @@ -57,7 +57,7 @@ At this time, only the following partners/publishers are eligble for pubId integ A. Do not have any display/banner inventory. B. Do not have any existing accounts on Yahoo SSP (aka: aol, oneMobile, oneDisplay). -# Mandaotory Bidder Parameters +# Mandatory Bidder Parameters ## dcn & pos (DEFAULT) The minimal requirements for the 'yahoossp' bid adapter to generate an outbound bid-request to our Yahoo SSP are: 1. At least 1 adUnit including mediaTypes: banner or video diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 994098cf5c8..c2f2b79a3b7 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -1,7 +1,7 @@ import { _each, isPlainObject, isArray, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import find from 'core-js-pure/features/array/find.js' -import { VIDEO, BANNER } from '../src/mediaTypes.js' +import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js' import { Renderer } from '../src/Renderer.js' import { config } from '../src/config.js'; @@ -15,7 +15,7 @@ const GVLID = 70 export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [VIDEO, BANNER], + supportedMediaTypes: [VIDEO, BANNER, NATIVE], isBidRequestValid: function (bid) { if (bid && bid.params && bid.params.adslotId && bid.params.supplyId) { @@ -149,6 +149,27 @@ export const spec = { } } + if (isNative(bidRequest, adType)) { + const url = `${ENDPOINT}/d/${matchedBid.id}/${bidRequest.params.supplyId}/?ts=${timestamp}${extId}${gdprApplies}${gdprConsent}${pvId}` + bidResponse.adUrl = url + bidResponse.mediaType = NATIVE + const nativeImageAssetObj = find(matchedBid.native.assets, e => e.id === 2) + const nativeImageAsset = nativeImageAssetObj ? nativeImageAssetObj.img : {url: '', w: 0, h: 0}; + const nativeTitleAsset = find(matchedBid.native.assets, e => e.id === 1) + const nativeBodyAsset = find(matchedBid.native.assets, e => e.id === 3) + bidResponse.native = { + title: nativeTitleAsset ? nativeTitleAsset.title.text : '', + body: nativeBodyAsset ? nativeBodyAsset.data.value : '', + image: { + url: nativeImageAsset.url, + width: nativeImageAsset.w, + height: nativeImageAsset.h, + }, + clickUrl: matchedBid.native.link.url, + impressionTrackers: matchedBid.native.imptrackers, + }; + } + bidResponses.push(bidResponse) } }) @@ -162,16 +183,26 @@ export const spec = { * @param {String} adtype * @returns {Boolean} */ -function isVideo (format, adtype) { +function isVideo(format, adtype) { return deepAccess(format, 'mediaTypes.video') && adtype.toLowerCase() === 'video' } +/** + * Is this a native format? + * @param {Object} format + * @param {String} adtype + * @returns {Boolean} + */ +function isNative(format, adtype) { + return deepAccess(format, 'mediaTypes.native') && adtype.toLowerCase() === 'native' +} + /** * Is this an outstream context? * @param {Object} format * @returns {Boolean} */ -function isOutstream (format) { +function isOutstream(format) { let context = deepAccess(format, 'mediaTypes.video.context') return (context === 'outstream') } @@ -181,7 +212,7 @@ function isOutstream (format) { * @param {Object} format * @returns {Array} */ -function getPlayerSize (format) { +function getPlayerSize(format) { let playerSize = deepAccess(format, 'mediaTypes.video.playerSize') return (playerSize && isArray(playerSize[0])) ? playerSize[0] : playerSize } @@ -191,7 +222,7 @@ function getPlayerSize (format) { * @param {String} size * @returns {Array} */ -function parseSize (size) { +function parseSize(size) { return size.split('x').map(Number) } @@ -200,7 +231,7 @@ function parseSize (size) { * @param {Array} eids * @returns {String} */ -function createUserIdString (eids) { +function createUserIdString(eids) { let str = [] for (let i = 0; i < eids.length; i++) { str.push(eids[i].source + ':' + eids[i].uids[0].id) @@ -213,7 +244,7 @@ function createUserIdString (eids) { * @param {Object} obj * @returns {String} */ -function createQueryString (obj) { +function createQueryString(obj) { let str = [] for (var p in obj) { if (obj.hasOwnProperty(p)) { @@ -233,7 +264,7 @@ function createQueryString (obj) { * @param {Object} obj * @returns {String} */ -function createTargetingString (obj) { +function createTargetingString(obj) { let str = [] for (var p in obj) { if (obj.hasOwnProperty(p)) { @@ -250,7 +281,7 @@ function createTargetingString (obj) { * @param {Object} schain * @returns {String} */ -function createSchainString (schain) { +function createSchainString(schain) { const ver = schain.ver || '' const complete = (schain.complete === 1 || schain.complete === 0) ? schain.complete : '' const keys = ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext'] diff --git a/modules/yieldlabBidAdapter.md b/modules/yieldlabBidAdapter.md index e3360ab10be..1f52e26f5c7 100644 --- a/modules/yieldlabBidAdapter.md +++ b/modules/yieldlabBidAdapter.md @@ -11,53 +11,96 @@ Maintainer: solutions@yieldlab.de Module that connects to Yieldlab's demand sources # Test Parameters + +```javascript +const adUnits = [ + { + code: 'banner', + sizes: [ [ 728, 90 ] ], + bids: [{ + bidder: 'yieldlab', + params: { + adslotId: '5220336', + supplyId: '1381604', + targeting: { + key1: 'value1', + key2: 'value2' + }, + extId: 'abc', + iabContent: { + id: 'some_id', + episode: '1', + title: 'some title', + series: 'some series', + season: 's1', + artist: 'John Doe', + genre: 'some genre', + isrc: 'CC-XXX-YY-NNNNN', + url: 'http://foo_url.de', + cat: [ 'IAB1-1', 'IAB1-2', 'IAB2-10' ], + context: '7', + keywords: ['k1', 'k2'], + live: '0' + } + } + }] + }, + { + code: 'video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + context: 'instream' // or 'outstream' + } + }, + bids: [{ + bidder: 'yieldlab', + params: { + adslotId: '5220339', + supplyId: '1381604' + } + }] + }, + { + code: 'native', + mediaTypes: { + native: { + // native config + } + }, + bids: [{ + bidder: 'yieldlab', + params: { + adslotId: '5220339', + supplyId: '1381604' + } + }] + } +]; ``` - var adUnits = [ - { - code: "banner", - sizes: [[728, 90]], - bids: [{ - bidder: "yieldlab", - params: { - adslotId: "5220336", - supplyId: "1381604", - targeting: { - key1: "value1", - key2: "value2" - }, - extId: "abc", - iabContent: { - id: "some_id", - episode: "1", - title: "some title", - series: "some series", - season: "s1", - artist: "John Doe", - genre: "some genre", - isrc: "CC-XXX-YY-NNNNN", - url: "http://foo_url.de", - cat: ["IAB1-1", "IAB1-2", "IAB2-10"], - context: "7", - keywords: ["k1", "k2"], - live: "0" - } - } - }] - }, { - code: "video", - sizes: [[640, 480]], - mediaTypes: { - video: { - context: "instream" // or "outstream" - } - }, - bids: [{ - bidder: "yieldlab", - params: { - adslotId: "5220339", - supplyId: "1381604" - } - }] - } - ]; + +# Multi-Format Setup + +A general overview of how to set up multi-format ads can be found in the offical Prebid.js docs. See: [show multi-format ads](https://docs.prebid.org/dev-docs/show-multi-format-ads.html) + +When setting up multi-format ads with Yieldlab make sure to always add at least one eligible Adslot per given media type in the ad unit configuration. + +```javascript +const adUnit = { + code: 'multi-format-adslot', + mediaTypes: { + banner: { + sizes: [ [ 728, 90 ] ] + }, + native: { + // native config + } + }, + bids: [ + // banner Adslot + { bidder: 'yieldlab', params: { adslotId: '1234', supplyId: '42' } }, + // native Adslot + { bidder: 'yieldlab', params: { adslotId: '2345', supplyId: '42' } } + ] +}; ``` diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index ed73a541b8b..6642d1e9b83 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -55,13 +55,8 @@ export const spec = { p: [], page_url: bidderRequest.refererInfo.referer, bust: new Date().getTime().toString(), - pr: (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || '', - scrd: LOCAL_WINDOW.devicePixelRatio || 0, dnt: getDNT(), description: getPageDescription(), - title: LOCAL_WINDOW.document.title || '', - w: LOCAL_WINDOW.innerWidth, - h: LOCAL_WINDOW.innerHeight, userConsent: JSON.stringify({ // case of undefined, stringify will remove param gdprApplies: deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', @@ -70,6 +65,14 @@ export const spec = { us_privacy: deepAccess(bidderRequest, 'uspConsent') || '' }; + if (canAccessTopWindow()) { + serverRequest.pr = (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || ''; + serverRequest.scrd = LOCAL_WINDOW.devicePixelRatio || 0; + serverRequest.title = LOCAL_WINDOW.document.title || ''; + serverRequest.w = LOCAL_WINDOW.innerWidth; + serverRequest.h = LOCAL_WINDOW.innerHeight; + } + const mtp = window.navigator.maxTouchPoints; if (mtp) { serverRequest.mtp = mtp; @@ -609,3 +612,18 @@ function getEids(bidRequest) { return createEidsArray(bidRequest.userId) || []; } }; + +/** + * Check if top window can be accessed + * + * @return {boolean} true if can access top window otherwise false + */ +function canAccessTopWindow() { + try { + if (getWindowTop().location.href) { + return true; + } + } catch (error) { + return false; + } +} diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index b12f314da2e..fe5a63cab51 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, isEmpty, parseSizesInput, isStr, logWarn } from '../src/utils.js'; +import {deepAccess, isEmpty, isStr, logWarn, parseSizesInput} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; @@ -11,6 +11,8 @@ const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/d const CMER_PLAYER_URL = 'https://an.cmertv.com/hb/renderer/cmertv-video-yone-prebid.min.js'; const VIEWABLE_PERCENTAGE_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/prebid-adformat-config.js'; +const DEFAULT_VIDEO_SIZE = {w: 640, h: 360}; + export const spec = { code: BIDDER_CODE, aliases: ['y1'], @@ -40,16 +42,18 @@ export const spec = { t: 'i' }; - const videoMediaType = deepAccess(bidRequest, 'mediaTypes.video'); - if ((isEmpty(bidRequest.mediaType) && isEmpty(bidRequest.mediaTypes)) || - (bidRequest.mediaType === BANNER || (bidRequest.mediaTypes && bidRequest.mediaTypes[BANNER]))) { - const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes; - payload.sz = parseSizesInput(sizes).join(','); - } else if (bidRequest.mediaType === VIDEO || videoMediaType) { - const sizes = deepAccess(bidRequest, 'mediaTypes.video.playerSize') || bidRequest.sizes; - const size = parseSizesInput(sizes)[0]; - payload.w = size.split('x')[0]; - payload.h = size.split('x')[1]; + const mediaType = getMediaType(bidRequest); + switch (mediaType) { + case BANNER: + payload.sz = getBannerSizes(bidRequest); + break; + case VIDEO: + const videoSize = getVideoSize(bidRequest); + payload.w = videoSize.w; + payload.h = videoSize.h; + break; + default: + break; } // LiveRampID @@ -167,6 +171,106 @@ export const spec = { }, } +/** + * NOTE: server side does not yet support multiple formats. + * @param {Object} bidRequest - + * @param {boolean} [enabledOldFormat = true] - default: `true`. + * @return {string|null} - `"banner"` or `"video"` or `null`. + */ +function getMediaType(bidRequest, enabledOldFormat = true) { + let hasBannerType = Boolean(deepAccess(bidRequest, 'mediaTypes.banner')); + let hasVideoType = Boolean(deepAccess(bidRequest, 'mediaTypes.video')); + + if (enabledOldFormat) { + hasBannerType = hasBannerType || bidRequest.mediaType === BANNER || + (isEmpty(bidRequest.mediaTypes) && isEmpty(bidRequest.mediaType)); + hasVideoType = hasVideoType || bidRequest.mediaType === VIDEO; + } + + if (hasBannerType && hasVideoType) { + const playerParams = deepAccess(bidRequest, 'params.playerParams') + if (playerParams) { + return VIDEO; + } else { + return BANNER; + } + } else if (hasBannerType) { + return BANNER; + } else if (hasVideoType) { + return VIDEO; + } + + return null; +} + +/** + * NOTE: + * If `mediaTypes.banner` exists, then `mediaTypes.banner.sizes` must also exist. + * The reason for this is that Prebid.js will perform the verification and + * if `mediaTypes.banner.sizes` is inappropriate, it will delete the entire `mediaTypes.banner`. + * @param {Object} bidRequest - + * @param {Object} bidRequest.banner - + * @param {Array} bidRequest.banner.sizes - + * @param {boolean} [enabledOldFormat = true] - default: `true`. + * @return {string} - strings like `"300x250"` or `"300x250,728x90"`. + */ +function getBannerSizes(bidRequest, enabledOldFormat = true) { + let sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); + + if (enabledOldFormat) { + sizes = sizes || bidRequest.sizes; + } + + return parseSizesInput(sizes).join(','); +} + +/** + * @param {Object} bidRequest - + * @param {boolean} [enabledOldFormat = true] - default: `true`. + * @param {boolean} [enabledFlux = true] - default: `true`. + * @return {{w: number, h: number}} - + */ +function getVideoSize(bidRequest, enabledOldFormat = true, enabledFlux = true) { + /** + * @param {Array | Array>} sizes - + * @return {{w: number, h: number} | null} - + */ + const _getPlayerSize = (sizes) => { + let result = null; + + const size = parseSizesInput(sizes)[0]; + if (isEmpty(size)) { + return result; + } + + const splited = size.split('x'); + const sizeObj = {w: parseInt(splited[0], 10), h: parseInt(splited[1], 10)}; + const _isValidPlayerSize = !(isEmpty(sizeObj)) && (isFinite(sizeObj.w) && isFinite(sizeObj.h)); + if (!_isValidPlayerSize) { + return result; + } + + result = sizeObj; + return result; + } + + let playerSize = _getPlayerSize(deepAccess(bidRequest, 'mediaTypes.video.playerSize')); + + if (enabledOldFormat) { + playerSize = playerSize || _getPlayerSize(bidRequest.sizes); + } + + if (enabledFlux) { + // NOTE: `video.playerSize` in Flux is always [1,1]. + if (playerSize && (playerSize.w === 1 && playerSize.h === 1)) { + // NOTE: `params.playerSize` is a specific object to support `FLUX`. + playerSize = _getPlayerSize(deepAccess(bidRequest, 'params.playerSize')); + } + } + + return playerSize || DEFAULT_VIDEO_SIZE; +} + function newRenderer(response) { const renderer = Renderer.install({ id: response.uid, diff --git a/modules/zetaSspBidAdapter.md b/modules/zetaSspBidAdapter.md index d2950bce6b9..00d8663586c 100644 --- a/modules/zetaSspBidAdapter.md +++ b/modules/zetaSspBidAdapter.md @@ -10,7 +10,7 @@ Maintainer: miakovlev@zetaglobal.com Module that connects to Zeta's SSP -# Test Parameters +# Banner Ad Unit: For Publishers ``` var adUnits = [ { @@ -40,3 +40,35 @@ Module that connects to Zeta's SSP } ]; ``` + +# Video Ad Unit: For Publishers +``` + var adUnits = [ + { + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + } + }, + bids: [ + { + bidder: 'zeta_global_ssp', + bidId: 12345, + params: { + placement: 12345, + user: { + uid: 12345, + buyeruid: 12345 + }, + tags: { + someTag: 123, + sid: 'publisherId' + }, + test: 1 + } + } + ] + } + ]; +``` diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js new file mode 100644 index 00000000000..906e6e19cc2 --- /dev/null +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -0,0 +1,97 @@ +import { logInfo, logError } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; + +import adapter from '../src/AnalyticsAdapter.js'; + +const ZETA_GVL_ID = 833; +const ADAPTER_CODE = 'zeta_global_ssp'; +const BASE_URL = 'https://ssp.disqus.com/prebid/event'; +const LOG_PREFIX = 'ZetaGlobalSsp-Analytics: '; + +/// /////////// VARIABLES //////////////////////////////////// + +let publisherId; // int + +/// /////////// HELPER FUNCTIONS ///////////////////////////// + +function sendEvent(eventType, event) { + ajax( + BASE_URL + '/' + eventType, + null, + JSON.stringify(event) + ); +} + +/// /////////// ADAPTER EVENT HANDLER FUNCTIONS ////////////// + +function adRenderSucceededHandler(args) { + let eventType = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED + logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); + + sendEvent(eventType, args); +} + +function auctionEndHandler(args) { + let eventType = CONSTANTS.EVENTS.AUCTION_END; + logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); + + sendEvent(eventType, args); +} + +/// /////////// ADAPTER DEFINITION /////////////////////////// + +let baseAdapter = adapter({ analyticsType: 'endpoint' }); +let zetaAdapter = Object.assign({}, baseAdapter, { + + enableAnalytics(config = {}) { + let error = false; + + if (typeof config.options === 'object') { + if (config.options.sid) { + publisherId = Number(config.options.sid); + } + } else { + logError(LOG_PREFIX + 'Config not found'); + error = true; + } + + if (!publisherId) { + logError(LOG_PREFIX + 'Missing sid (publisher id)'); + error = true; + } + + if (error) { + logError(LOG_PREFIX + 'Analytics is disabled due to error(s)'); + } else { + baseAdapter.enableAnalytics.call(this, config); + } + }, + + disableAnalytics() { + publisherId = undefined; + baseAdapter.disableAnalytics.apply(this, arguments); + }, + + track({ eventType, args }) { + switch (eventType) { + case CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED: + adRenderSucceededHandler(args); + break; + case CONSTANTS.EVENTS.AUCTION_END: + auctionEndHandler(args); + break; + } + } +}); + +/// /////////// ADAPTER REGISTRATION ///////////////////////// + +adapterManager.registerAnalyticsAdapter({ + adapter: zetaAdapter, + code: ADAPTER_CODE, + gvlid: ZETA_GVL_ID +}); + +export default zetaAdapter; diff --git a/modules/zeta_global_sspAnalyticsAdapter.md b/modules/zeta_global_sspAnalyticsAdapter.md new file mode 100644 index 00000000000..d586d0069b1 --- /dev/null +++ b/modules/zeta_global_sspAnalyticsAdapter.md @@ -0,0 +1,24 @@ +# Zeta Global SSP Analytics Adapter + +## Overview + +Module Name: Zeta Global SSP Analytics Adapter\ +Module Type: Analytics Adapter\ +Maintainer: abermanov@zetaglobal.com + +## Description + +Analytics Adapter which sends auctionEnd and adRenderSucceeded events to Zeta Global SSP analytics endpoints + +## How to configure +``` +pbjs.enableAnalytics({ + provider: 'zeta_global_ssp', + options: { + sid: 111, + tags: { + ... + } + } +}); +``` diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index f526a50e098..77542252aeb 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -1,4 +1,4 @@ -import { logWarn, deepSetValue, deepAccess, isArray, isNumber, isBoolean, isStr } from '../src/utils.js'; +import {deepAccess, deepSetValue, isArray, isBoolean, isNumber, isStr, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -11,6 +11,8 @@ const DEFAULT_CUR = 'USD'; const TTL = 200; const NET_REV = true; +const VIDEO_REGEX = new RegExp(/VAST\s+version/); + const DATA_TYPES = { 'NUMBER': 'number', 'STRING': 'string', @@ -161,6 +163,7 @@ export const spec = { advertiserDomains: zetaBid.adomain }; } + provideMediaType(zetaBid, bid); bidResponses.push(bid); }) }) @@ -284,4 +287,22 @@ function isConnectedTV() { return /(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i.test(navigator.userAgent); } +function provideMediaType(zetaBid, bid) { + if (zetaBid.ext && zetaBid.ext.bidtype) { + if (zetaBid.ext.bidtype === VIDEO) { + bid.mediaType = VIDEO; + bid.vastXml = bid.ad; + } else { + bid.mediaType = BANNER; + } + } else { + if (VIDEO_REGEX.test(bid.ad)) { + bid.mediaType = VIDEO; + bid.vastXml = bid.ad; + } else { + bid.mediaType = BANNER; + } + } +} + registerBidder(spec); diff --git a/package-lock.json b/package-lock.json index d49458b682a..50296a6ae96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "5.20.4", + "version": "5.3.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 24b3f279c70..a1c66fa3d48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "5.20.4", + "version": "6.7.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -11,6 +11,11 @@ "type": "git", "url": "https://github.com/prebid/Prebid.js.git" }, + "browserslist": [ + "> 0.25%", + "not IE 11", + "not op_mini all" + ], "keywords": [ "advertising", "auction", diff --git a/plugins/pbjsGlobals.js b/plugins/pbjsGlobals.js index bf3c9033ee6..73912d8126e 100644 --- a/plugins/pbjsGlobals.js +++ b/plugins/pbjsGlobals.js @@ -1,11 +1,13 @@ let t = require('@babel/core').types; let prebid = require('../package.json'); +const path = require('path'); module.exports = function(api, options) { + const pbGlobal = options.globalVarName || prebid.globalVarName; let replace = { '$prebid.version$': prebid.version, - '$$PREBID_GLOBAL$$': options.globalVarName || prebid.globalVarName, + '$$PREBID_GLOBAL$$': pbGlobal, '$$REPO_AND_VERSION$$': `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}` }; @@ -13,8 +15,33 @@ module.exports = function(api, options) { '$$REPO_AND_VERSION$$' ]; + const PREBID_ROOT = path.resolve(__dirname, '..'); + + function getModuleName(filename) { + const modPath = path.parse(path.relative(PREBID_ROOT, filename)); + if (modPath.ext.toLowerCase() !== '.js') { + return null; + } + if (modPath.dir === 'modules') { + // modules/moduleName.js -> moduleName + return modPath.name; + } + if (modPath.name.toLowerCase() === 'index' && path.dirname(modPath.dir) === 'modules') { + // modules/moduleName/index.js -> moduleName + return path.basename(modPath.dir); + } + return null; + } + return { visitor: { + Program(path, state) { + const modName = getModuleName(state.filename); + if (modName != null) { + // append "registration" of module file to $$PREBID_GLOBAL$$.installedModules + path.node.body.push(...api.parse(`window.${pbGlobal}.installedModules.push('${modName}');`).program.body); + } + }, StringLiteral(path) { Object.keys(replace).forEach(name => { if (path.node.value.includes(name)) { diff --git a/src/adapterManager.js b/src/adapterManager.js index 3ee0dff81ad..9a041543cf8 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -262,14 +262,16 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a } let adUnitsS2SCopy = getAdUnitCopyForPrebidServer(adUnits, s2sConfig); - let tid = generateUUID(); + + // uniquePbsTid is so we know which server to send which bids to during the callBids function + let uniquePbsTid = generateUUID(); adaptersServerSide.forEach(bidderCode => { const bidderRequestId = getUniqueIdentifierStr(); const bidderRequest = { bidderCode, auctionId, bidderRequestId, - tid, + uniquePbsTid, bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': deepClone(adUnitsS2SCopy), labels, src: CONSTANTS.S2S.SRC}), auctionStart: auctionStart, timeout: s2sConfig.timeout, @@ -350,7 +352,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request serverBidRequests.forEach(serverBidRequest => { var index = -1; for (var i = 0; i < uniqueServerBidRequests.length; ++i) { - if (serverBidRequest.tid === uniqueServerBidRequests[i].tid) { + if (serverBidRequest.uniquePbsTid === uniqueServerBidRequests[i].uniquePbsTid) { index = i; break; } @@ -360,7 +362,10 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request } }); - let counter = 0 + let counter = 0; + + // $.source.tid MUST be a unique UUID and also THE SAME between all PBS Requests for a given Auction + const sourceTid = generateUUID(); _s2sConfigs.forEach((s2sConfig) => { if (s2sConfig && uniqueServerBidRequests[counter] && includes(s2sConfig.bidders, uniqueServerBidRequests[counter].bidderCode)) { // s2s should get the same client side timeout as other client side requests. @@ -370,13 +375,13 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request } : undefined); let adaptersServerSide = s2sConfig.bidders; const s2sAdapter = _bidderRegistry[s2sConfig.adapter]; - let tid = uniqueServerBidRequests[counter].tid; + let uniquePbsTid = uniqueServerBidRequests[counter].uniquePbsTid; let adUnitsS2SCopy = uniqueServerBidRequests[counter].adUnitsS2SCopy; - let uniqueServerRequests = serverBidRequests.filter(serverBidRequest => serverBidRequest.tid === tid) + let uniqueServerRequests = serverBidRequests.filter(serverBidRequest => serverBidRequest.uniquePbsTid === uniquePbsTid); if (s2sAdapter) { - let s2sBidRequest = {tid, 'ad_units': adUnitsS2SCopy, s2sConfig}; + let s2sBidRequest = {tid: sourceTid, 'ad_units': adUnitsS2SCopy, s2sConfig}; if (s2sBidRequest.ad_units.length) { let doneCbs = uniqueServerRequests.map(bidRequest => { bidRequest.start = timestamp(); @@ -391,7 +396,8 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request // fire BID_REQUESTED event for each s2s bidRequest uniqueServerRequests.forEach(bidRequest => { - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + // add the new sourceTid + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, {...bidRequest, tid: sourceTid}); }); // make bid requests diff --git a/src/auction.js b/src/auction.js index 059c09bc2ff..175f08439d1 100644 --- a/src/auction.js +++ b/src/auction.js @@ -59,7 +59,7 @@ import { flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue, parseUrl, generateUUID, - logMessage, bind, logError, logInfo, logWarn, isEmpty, _each, isFn, isEmptyStr + logMessage, bind, logError, logInfo, logWarn, isEmpty, _each, isFn, isEmptyStr, isAllowZeroCpmBidsEnabled } from './utils.js'; import { getPriceBucketString } from './cpmBucketManager.js'; import { getNativeTargeting } from './native.js'; @@ -567,8 +567,9 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { function setupBidTargeting(bidObject, bidderRequest) { let keyValues; - if (bidObject.bidderCode && (bidObject.cpm > 0 || bidObject.dealId)) { - let bidReq = find(bidderRequest.bids, bid => bid.adUnitCode === bidObject.adUnitCode); + const cpmCheck = (isAllowZeroCpmBidsEnabled(bidObject.bidderCode)) ? bidObject.cpm >= 0 : bidObject.cpm > 0; + if (bidObject.bidderCode && (cpmCheck || bidObject.dealId)) { + let bidReq = find(bidderRequest.bids, bid => bid.adUnitCode === bidObject.adUnitCode && bid.bidId === bidObject.requestId); keyValues = getKeyValueTargetingPairs(bidObject.bidderCode, bidObject, bidReq); } @@ -743,7 +744,7 @@ function setKeys(keyValues, bidderSettings, custBidObj, bidReq) { var value = kvPair.val; if (keyValues[key]) { - logWarn('The key: ' + key + ' is getting ovewritten'); + logWarn('The key: ' + key + ' is being overwritten'); } if (isFn(value)) { diff --git a/src/config.js b/src/config.js index 72d760c5b87..ddf3b79a6af 100644 --- a/src/config.js +++ b/src/config.js @@ -1,6 +1,6 @@ /* * Module for getting and setting Prebid configuration. - */ +*/ /** * @typedef {Object} MediaTypePriceGranularity @@ -580,7 +580,7 @@ export function newConfig() { .forEach(listener => listener.callback(options)); } - function setBidderConfig(config) { + function setBidderConfig(config, mergeFlag = false) { try { check(config); config.bidders.forEach(bidder => { @@ -592,7 +592,8 @@ export function newConfig() { let option = (topic === 'fpd') ? convertFpd(config.config[topic]) : config.config[topic]; if (isPlainObject(option)) { - bidderConfig[bidder][prop] = Object.assign({}, bidderConfig[bidder][prop] || {}, option); + const func = mergeFlag ? mergeDeep : Object.assign; + bidderConfig[bidder][prop] = func({}, bidderConfig[bidder][prop] || {}, option); } else { bidderConfig[bidder][prop] = option; } @@ -601,6 +602,7 @@ export function newConfig() { } catch (e) { logError(e); } + function check(obj) { if (!isPlainObject(obj)) { throw 'setBidderConfig bidder options must be an object'; @@ -614,6 +616,26 @@ export function newConfig() { } } + function mergeConfig(obj) { + if (!isPlainObject(obj)) { + logError('mergeConfig input must be an object'); + return; + } + + const mergedConfig = Object.keys(obj).reduce((accum, key) => { + const prevConf = _getConfig(key)[key] || {}; + accum[key] = mergeDeep(prevConf, obj[key]); + return accum; + }, {}); + + setConfig({ ...mergedConfig }); + return mergedConfig; + } + + function mergeBidderConfig(obj) { + return setBidderConfig(obj, true); + } + /** * Internal functions for core to execute some synchronous code while having an active bidder set. */ @@ -653,12 +675,14 @@ export function newConfig() { getConfig, readConfig, setConfig, + mergeConfig, setDefaults, resetConfig, runWithBidder, callbackWithBidder, setBidderConfig, getBidderConfig, + mergeBidderConfig, convertAdUnitFpd, getLegacyFpd, getLegacyImpFpd diff --git a/src/hook.js b/src/hook.js index 9050bf2f7dc..2c8e4c7a6e7 100644 --- a/src/hook.js +++ b/src/hook.js @@ -13,14 +13,18 @@ export function setupBeforeHookFnOnce(baseFn, hookFn, priority = 15) { baseFn.before(hookFn, priority); } } +const submoduleInstallMap = {}; -export function module(name, install) { +export function module(name, install, {postInstallAllowed = false} = {}) { hook('async', function (submodules) { submodules.forEach(args => install(...args)); + if (postInstallAllowed) submoduleInstallMap[name] = install; }, name)([]); // will be queued until hook.ready() called in pbjs.processQueue(); } export function submodule(name, ...args) { + const install = submoduleInstallMap[name]; + if (install) return install(...args); getHook(name).before((next, modules) => { modules.push(args); next(modules); diff --git a/src/prebid.js b/src/prebid.js index 2f9290e8f1b..e59ee6cded6 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -47,8 +47,7 @@ $$PREBID_GLOBAL$$.libLoaded = true; $$PREBID_GLOBAL$$.version = 'v$prebid.version$'; logInfo('Prebid.js v$prebid.version$ loaded'); -// modules list generated from build -$$PREBID_GLOBAL$$.installedModules = ['v$prebid.modulesList$']; +$$PREBID_GLOBAL$$.installedModules = $$PREBID_GLOBAL$$.installedModules || []; // create adUnit array $$PREBID_GLOBAL$$.adUnits = $$PREBID_GLOBAL$$.adUnits || []; @@ -463,8 +462,6 @@ $$PREBID_GLOBAL$$.renderAd = hook('async', function (doc, id, options) { const {height, width, ad, mediaType, adUrl, renderer} = bid; const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); - // It is important that the comment with metadata is injected before the ad is actually rendered, - // so the creativeId can be used by e.g. ad quality scanners to check against blocking rules insertElement(creativeComment, doc, 'html'); if (isRendererRequired(renderer)) { @@ -918,6 +915,8 @@ $$PREBID_GLOBAL$$.markWinningBidAsUsed = function (markBidRequest) { */ $$PREBID_GLOBAL$$.getConfig = config.getConfig; $$PREBID_GLOBAL$$.readConfig = config.readConfig; +$$PREBID_GLOBAL$$.mergeConfig = config.mergeConfig; +$$PREBID_GLOBAL$$.mergeBidderConfig = config.mergeBidderConfig; /** * Set Prebid config options. diff --git a/src/secureCreatives.js b/src/secureCreatives.js index c82d1375015..01a84ae254a 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -127,11 +127,12 @@ function resizeRemoteCreative({ adId, adUnitCode, width, height }) { } function getDfpElementId(adId) { - return find(window.googletag.pubads().getSlots(), slot => { + const slot = find(window.googletag.pubads().getSlots(), slot => { return find(slot.getTargetingKeys(), key => { return includes(slot.getTargeting(key), adId); }); - }).getSlotElementId(); + }); + return slot ? slot.getSlotElementId() : null; } function getAstElementId(adUnitCode) { diff --git a/src/targeting.js b/src/targeting.js index 3cc18edc032..423e946896e 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -1,6 +1,6 @@ import { uniques, isGptPubadsDefined, getHighestCpm, getOldestHighestCpmBid, groupBy, isAdUnitCodeMatchingSlot, timestamp, - deepAccess, deepClone, logError, logWarn, logInfo, isFn, isArray, logMessage, isStr + deepAccess, deepClone, logError, logWarn, logInfo, isFn, isArray, logMessage, isStr, isAllowZeroCpmBidsEnabled } from './utils.js'; import { config } from './config.js'; import { NATIVE_TARGETING_KEYS } from './native.js'; @@ -18,6 +18,10 @@ var pbTargetingKeys = []; const MAX_DFP_KEYLENGTH = 20; const TTL_BUFFER = 1000; +const CFG_ALLOW_TARGETING_KEYS = `targetingControls.allowTargetingKeys`; +const CFG_ADD_TARGETING_KEYS = `targetingControls.addTargetingKeys`; +const TARGETING_KEY_CONFIGURATION_ERROR_MSG = `Only one of "${CFG_ALLOW_TARGETING_KEYS}" or "${CFG_ADD_TARGETING_KEYS}" can be set`; + export const TARGETING_KEYS = Object.keys(CONSTANTS.TARGETING_KEYS).map( key => CONSTANTS.TARGETING_KEYS[key] ); @@ -261,7 +265,17 @@ export function newTargeting(auctionManager) { }); const defaultKeys = Object.keys(Object.assign({}, CONSTANTS.DEFAULT_TARGETING_KEYS, CONSTANTS.NATIVE_KEYS)); - const allowedKeys = config.getConfig('targetingControls.allowTargetingKeys') || defaultKeys; + let allowedKeys = config.getConfig(CFG_ALLOW_TARGETING_KEYS); + const addedKeys = config.getConfig(CFG_ADD_TARGETING_KEYS); + + if (addedKeys != null && allowedKeys != null) { + throw new Error(TARGETING_KEY_CONFIGURATION_ERROR_MSG); + } else if (addedKeys != null) { + allowedKeys = defaultKeys.concat(addedKeys); + } else { + allowedKeys = allowedKeys || defaultKeys; + } + if (Array.isArray(allowedKeys) && allowedKeys.length > 0) { targeting = getAllowedTargetingKeyValues(targeting, allowedKeys); } @@ -284,6 +298,13 @@ export function newTargeting(auctionManager) { return targeting; }; + // warn about conflicting configuration + config.getConfig('targetingControls', function (config) { + if (deepAccess(config, CFG_ALLOW_TARGETING_KEYS) != null && deepAccess(config, CFG_ADD_TARGETING_KEYS) != null) { + logError(TARGETING_KEY_CONFIGURATION_ERROR_MSG); + } + }); + // create an encoded string variant based on the keypairs of the provided object // - note this will encode the characters between the keys (ie = and &) function convertKeysToQueryForm(keyMap) { @@ -416,15 +437,7 @@ export function newTargeting(auctionManager) { let bidsReceived = auctionManager.getBidsReceived(); if (!config.getConfig('useBidCache')) { - // don't use bid cache (i.e. filter out bids not in the latest auction) bidsReceived = bidsReceived.filter(bid => latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId) - } else { - // if custom bid cache filter function exists, run for each bid from - // previous auctions. If it returns true, include bid in bid pool - const filterFunction = config.getConfig('bidCacheFilterFunction'); - if (typeof filterFunction === 'function') { - bidsReceived = bidsReceived.filter(bid => latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId || !!filterFunction(bid)) - } } bidsReceived = bidsReceived @@ -446,7 +459,7 @@ export function newTargeting(auctionManager) { const adUnitCodes = getAdUnitCodes(adUnitCode); return bidsReceived .filter(bid => includes(adUnitCodes, bid.adUnitCode)) - .filter(bid => bid.cpm > 0) + .filter(bid => (isAllowZeroCpmBidsEnabled(bid.bidderCode)) ? bid.cpm >= 0 : bid.cpm > 0) .map(bid => bid.adUnitCode) .filter(uniques) .map(adUnitCode => bidsReceived diff --git a/src/utils.js b/src/utils.js index 03c76529ddf..cbe1b1665aa 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,6 @@ /* eslint-disable no-console */ import { config } from './config.js'; +import { getGlobal } from './prebidGlobal.js'; import clone from 'just-clone'; import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; @@ -481,16 +482,43 @@ export function insertElement(elm, doc, target, asLastChildChild) { } catch (e) {} } +/** + * Returns a promise that completes when the given element triggers a 'load' or 'error' DOM event, or when + * `timeout` milliseconds have elapsed. + * + * @param {HTMLElement} element + * @param {Number} [timeout] + * @returns {Promise} + */ +export function waitForElementToLoad(element, timeout) { + let timer = null; + return new Promise((resolve) => { + const onLoad = function() { + element.removeEventListener('load', onLoad); + element.removeEventListener('error', onLoad); + if (timer != null) { + window.clearTimeout(timer); + } + resolve(); + }; + element.addEventListener('load', onLoad); + element.addEventListener('error', onLoad); + if (timeout != null) { + timer = window.setTimeout(onLoad, timeout); + } + }); +} + /** * Inserts an image pixel with the specified `url` for cookie sync * @param {string} url URL string of the image pixel to load * @param {function} [done] an optional exit callback, used when this usersync pixel is added during an async process + * @param {Number} [timeout] an optional timeout in milliseconds for the image to load before calling `done` */ -export function triggerPixel(url, done) { +export function triggerPixel(url, done, timeout) { const img = new Image(); if (done && internal.isFn(done)) { - img.addEventListener('load', done); - img.addEventListener('error', done); + waitForElementToLoad(img, timeout).then(done); } img.src = url; } @@ -538,18 +566,18 @@ export function insertHtmlIntoIframe(htmlCode) { * @param {string} url URL to be requested * @param {string} encodeUri boolean if URL should be encoded before inserted. Defaults to true * @param {function} [done] an optional exit callback, used when this usersync pixel is added during an async process + * @param {Number} [timeout] an optional timeout in milliseconds for the iframe to load before calling `done` */ -export function insertUserSyncIframe(url, done) { +export function insertUserSyncIframe(url, done, timeout) { let iframeHtml = internal.createTrackPixelIframeHtml(url, false, 'allow-scripts allow-same-origin'); let div = document.createElement('div'); div.innerHTML = iframeHtml; let iframe = div.firstChild; if (done && internal.isFn(done)) { - iframe.addEventListener('load', done); - iframe.addEventListener('error', done); + waitForElementToLoad(iframe, timeout).then(done); } internal.insertElement(iframe, document, 'html', true); -}; +} /** * Creates a snippet of HTML that retrieves the specified `url` @@ -1244,7 +1272,18 @@ export function mergeDeep(target, ...sources) { if (!target[key]) { Object.assign(target, { [key]: source[key] }); } else if (isArray(target[key])) { - target[key] = target[key].concat(source[key]); + source[key].forEach(obj => { + let addItFlag = 1; + for (let i = 0; i < target[key].length; i++) { + if (deepEqual(target[key][i], obj)) { + addItFlag = 0; + break; + } + } + if (addItFlag) { + target[key].push(obj); + } + }); } } else { Object.assign(target, { [key]: source[key] }); @@ -1294,3 +1333,9 @@ export function cyrb53Hash(str, seed = 0) { h2 = imul(h2 ^ (h2 >>> 16), 2246822507) ^ imul(h1 ^ (h1 >>> 13), 3266489909); return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(); } + +export function isAllowZeroCpmBidsEnabled(bidderCode) { + const bidderSettings = getGlobal().bidderSettings; + return ((bidderSettings[bidderCode] && bidderSettings[bidderCode].allowZeroCpmBids === true) || + (bidderSettings.standard && bidderSettings.standard.allowZeroCpmBids === true)); +} diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index b0fbd7da806..908382f8daa 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -1231,7 +1231,7 @@ export function getCurrencyRates() { }; } -export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, adUnitCode, adId, status, ttl, requestId, mediaType}) { +export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, adUnitCode, adId, status, ttl, requestId}) { let bid = { 'bidderCode': bidder, 'width': '300', @@ -1259,7 +1259,6 @@ export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, ad 'hb_pb': cpm, 'foobar': '300x250' }), - 'mediaType': mediaType, 'netRevenue': true, 'currency': 'USD', 'ttl': (!ttl) ? 300 : ttl diff --git a/test/helpers/prebidGlobal.js b/test/helpers/prebidGlobal.js index 597076ab0db..94776a5242b 100644 --- a/test/helpers/prebidGlobal.js +++ b/test/helpers/prebidGlobal.js @@ -1,3 +1,4 @@ window.$$PREBID_GLOBAL$$ = (window.$$PREBID_GLOBAL$$ || {}); +window.$$PREBID_GLOBAL$$.installedModules = (window.$$PREBID_GLOBAL$$.installedModules || []); window.$$PREBID_GLOBAL$$.cmd = window.$$PREBID_GLOBAL$$.cmd || []; window.$$PREBID_GLOBAL$$.que = window.$$PREBID_GLOBAL$$.que || []; diff --git a/test/spec/config_spec.js b/test/spec/config_spec.js index 6cc1bd557d5..04501ccb5b5 100644 --- a/test/spec/config_spec.js +++ b/test/spec/config_spec.js @@ -7,8 +7,10 @@ const utils = require('src/utils'); let getConfig; let setConfig; let readConfig; +let mergeConfig; let getBidderConfig; let setBidderConfig; +let mergeBidderConfig; let setDefaults; describe('config API', function () { @@ -19,8 +21,10 @@ describe('config API', function () { getConfig = config.getConfig; setConfig = config.setConfig; readConfig = config.readConfig; + mergeConfig = config.mergeConfig; getBidderConfig = config.getBidderConfig; setBidderConfig = config.setBidderConfig; + mergeBidderConfig = config.mergeBidderConfig; setDefaults = config.setDefaults; logErrorSpy = sinon.spy(utils, 'logError'); logWarnSpy = sinon.spy(utils, 'logWarn'); @@ -345,4 +349,279 @@ describe('config API', function () { const warning = 'Auction Options given an incorrect param: testing'; assert.ok(logWarnSpy.calledWith(warning), 'expected warning was logged'); }); + + it('should merge input with existing global config', function () { + const obj = { + ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + } + } + }; + setConfig({ ortb2: { + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + }); + mergeConfig(obj); + const expected = { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + }, + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + expect(getConfig('ortb2')).to.deep.equal(expected); + }); + + it('input should take precedence over existing config if keys are the same', function() { + const input = { + ortb2: { + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + } + setConfig({ ortb2: { + user: { + ext: { + data: { + registered: false + } + } + } + }}); + mergeConfig(input); + const expected = { + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + expect(getConfig('ortb2')).to.deep.equal(expected); + }); + + it('should log error for a non-object value passed in', function () { + mergeConfig('invalid object'); + expect(logErrorSpy.calledOnce).to.equal(true); + const error = 'mergeConfig input must be an object'; + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + }); + + it('should merge input with existing bidder config', function () { + const input = { + bidders: ['rubicon', 'appnexus'], + config: { + ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + }, + user: { + ext: { + ssp: 'magnite', + data: { + registered: false, + interests: ['sports'] + } + } + } + } + } + }; + setBidderConfig({ + bidders: ['rubicon'], + config: { + ortb2: { + user: { + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + } + }); + mergeBidderConfig(input); + const expected = { + rubicon: { + ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + }, + user: { + ext: { + ssp: 'magnite', + data: { + registered: false, + interests: ['cars', 'sports'] + } + } + } + } + }, + appnexus: { + ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + }, + user: { + ext: { + ssp: 'magnite', + data: { + registered: false, + interests: ['sports'] + } + } + } + } + } + } + expect(getBidderConfig()).to.deep.equal(expected); + }); + + it('should log error for a non-object value passed in', function () { + mergeBidderConfig('invalid object'); + expect(logErrorSpy.calledOnce).to.equal(true); + const error = 'setBidderConfig bidder options must be an object'; + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + }); + + it('should log error for empty bidders array', function () { + mergeBidderConfig({ + bidders: [], + config: { + ortb2: { + site: { + name: 'example', + domain: 'page.example.com', + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'] + } + } + } + }); + expect(logErrorSpy.calledOnce).to.equal(true); + const error = 'setBidderConfig bidder options must contain a bidders list with at least 1 bidder'; + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + }); + + it('should log error for nonexistent config object', function () { + mergeBidderConfig({ + bidders: ['appnexus'] + }); + expect(logErrorSpy.calledOnce).to.equal(true); + const error = 'setBidderConfig bidder options must contain a config object'; + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + }); + + it('should merge without array duplication', function() { + const userObj1 = { + name: 'www.dataprovider1.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1776' + }] + }; + + const userObj2 = { + name: 'www.dataprovider2.com', + ext: { taxonomyname: 'iab_audience_taxonomy' }, + segment: [{ + id: '1914' + }] + }; + + const siteObj1 = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1812' + }, + { + id: '1955' + } + ] + } + + setConfig({ + ortb2: { + user: { + data: [userObj1, userObj2] + }, + site: { + content: { + data: [siteObj1] + } + } + } + }); + + const rtd = { + ortb2: { + user: { + data: [userObj1] + }, + site: { + content: { + data: [siteObj1] + } + } + } + }; + mergeConfig(rtd); + + let ortb2Config = getConfig('ortb2'); + + expect(ortb2Config.user.data).to.deep.include.members([userObj1, userObj2]); + expect(ortb2Config.site.content.data).to.deep.include.members([siteObj1]); + expect(ortb2Config.user.data).to.have.lengthOf(2); + expect(ortb2Config.site.content.data).to.have.lengthOf(1); + }); }); diff --git a/test/spec/integration/faker/googletag.js b/test/spec/integration/faker/googletag.js index 1d1a7512153..a8676b500a5 100644 --- a/test/spec/integration/faker/googletag.js +++ b/test/spec/integration/faker/googletag.js @@ -51,9 +51,15 @@ export function enable() { window.googletag = { _slots: [], _callbackMap: {}, + _ppid: undefined, + cmd: [], pubads: function () { var self = this; return { + setPublisherProvidedId: function (ppid) { + self._ppid = ppid; + }, + getSlots: function () { return self._slots; }, diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index b5443cdd5c2..141edc1e61c 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -16,20 +16,21 @@ function validateBuiltServerRequest(builtReq, expectedReq) { describe('33acrossBidAdapter:', function () { const BIDDER_CODE = '33across'; const SITE_ID = 'sample33xGUID123456789'; - const PRODUCT_ID = 'siab'; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; let element, win; let bidRequests; let sandbox; - function TtxRequestBuilder() { + function TtxRequestBuilder(siteId = SITE_ID) { const ttxRequest = { - imp: [{}], + imp: [{ + id: 'b1' + }], site: { - id: SITE_ID + id: siteId }, - id: 'b1', + id: 'r1', regs: { ext: { gdpr: 0 @@ -46,66 +47,83 @@ describe('33acrossBidAdapter:', function () { } }; + this.addImp = (id = 'b2') => { + ttxRequest.imp.push({ id }); + + return this; + } + this.withBanner = () => { - Object.assign(ttxRequest.imp[0], { - banner: { - format: [ - { - w: 300, - h: 250 - }, - { - w: 728, - h: 90 - } - ], - ext: { - ttx: { - viewability: { - amount: 100 + ttxRequest.imp.forEach((imp) => { + Object.assign(imp, { + banner: { + format: [ + { + w: 300, + h: 250 + }, + { + w: 728, + h: 90 + } + ], + ext: { + ttx: { + viewability: { + amount: 100 + } } } } - } + }); }); - return this; }; this.withBannerSizes = this.withSizes = sizes => { - Object.assign(ttxRequest.imp[0].banner, { format: sizes }); + ttxRequest.imp.forEach((imp) => { + Object.assign(imp.banner, { format: sizes }); + }); + return this; }; this.withVideo = (params = {}) => { - Object.assign(ttxRequest.imp[0], { - video: { - w: 300, - h: 250, - placement: 2, - ...params - } + ttxRequest.imp.forEach((imp) => { + Object.assign(imp, { + video: { + w: 300, + h: 250, + placement: 2, + ...params + } + }); }); return this; }; this.withViewability = (viewability, format = 'banner') => { - Object.assign(ttxRequest.imp[0][format], { - ext: { - ttx: { viewability } - } + ttxRequest.imp.forEach((imp) => { + Object.assign(imp[format], { + ext: { + ttx: { viewability } + } + }); }); + return this; }; - this.withProduct = (prod = PRODUCT_ID) => { - Object.assign(ttxRequest.imp[0], { - ext: { - ttx: { - prod + this.withProduct = (prod = 'siab') => { + ttxRequest.imp.forEach((imp) => { + Object.assign(imp, { + ext: { + ttx: { + prod + } } - } + }); }); return this; @@ -249,7 +267,7 @@ describe('33acrossBidAdapter:', function () { bidderRequestId: 'b1a', params: { siteId: SITE_ID, - productId: PRODUCT_ID + productId: 'siab' }, adUnitCode: 'div-id', auctionId: 'r1', @@ -258,35 +276,61 @@ describe('33acrossBidAdapter:', function () { } ]; + this.addBid = (bidParams = {}) => { + bidRequests.push({ + bidId: 'b2', + bidder: '33across', + bidderRequestId: 'b1b', + params: { + siteId: SITE_ID, + productId: 'siab' + }, + adUnitCode: 'div-id', + auctionId: 'r1', + mediaTypes: {}, + transactionId: 't2', + ...bidParams + }); + + return this; + }; + this.withBanner = () => { - bidRequests[0].mediaTypes.banner = { - sizes: [ - [300, 250], - [728, 90] - ] - }; + bidRequests.forEach((bid) => { + bid.mediaTypes.banner = { + sizes: [ + [300, 250], + [728, 90] + ] + }; + }); return this; }; this.withProduct = (prod) => { - bidRequests[0].params.productId = prod; - + bidRequests.forEach((bid) => { + bid.params.productId = prod; + }); return this; }; this.withVideo = (params) => { - bidRequests[0].mediaTypes.video = { - playerSize: [[300, 250]], - context: 'outstream', - ...params - }; + bidRequests.forEach((bid) => { + bid.mediaTypes.video = { + playerSize: [[300, 250]], + context: 'outstream', + ...params + }; + }); return this; } this.withUserIds = (eids) => { - bidRequests[0].userIdAsEids = eids; + bidRequests.forEach((bid) => { + bid.userIdAsEids = eids; + }); return this; }; @@ -315,6 +359,7 @@ describe('33acrossBidAdapter:', function () { } }; win = { + parent: null, document: { visibilityState: 'visible' }, @@ -331,7 +376,7 @@ describe('33acrossBidAdapter:', function () { sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1); - sandbox.stub(document, 'getElementById').withArgs('div-id').returns(element); + sandbox.stub(document, 'getElementById').returns(element); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); }); @@ -1376,10 +1421,146 @@ describe('33acrossBidAdapter:', function () { }); }); }); + + context('when SRA mode is enabled', function() { + it('builds a single request with multiple imps corresponding to each group {siteId, productId}', function() { + sandbox.stub(config, 'getConfig').callsFake(() => { + return { + enableSRAMode: true + } + }); + + const bidRequests = new BidRequestsBuilder() + .addBid() + .addBid({ + bidId: 'b3', + adUnitCode: 'div-id', + params: { + siteId: 'sample33xGUID123456780', + productId: 'siab' + } + }) + .addBid({ + bidId: 'b4', + adUnitCode: 'div-id', + params: { + siteId: 'sample33xGUID123456780', + productId: 'inview' + } + }) + .withBanner() + .withVideo({context: 'outstream'}) + .build(); + + const req1 = new TtxRequestBuilder() + .addImp() + .withProduct('siab') + .withBanner() + .withVideo() + .build(); + + const req2 = new TtxRequestBuilder('sample33xGUID123456780') + .withProduct('siab') + .withBanner() + .withVideo() + .build(); + + req2.imp[0].id = 'b3'; + + const req3 = new TtxRequestBuilder('sample33xGUID123456780') + .withProduct('inview') + .withBanner() + .withVideo() + .build(); + + req3.imp[0].id = 'b4'; + + const serverReq1 = new ServerRequestBuilder() + .withData(req1) + .build(); + + const serverReq2 = new ServerRequestBuilder() + .withData(req2) + .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') + .build(); + + const serverReq3 = new ServerRequestBuilder() + .withData(req3) + .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests).to.deep.equal([serverReq1, serverReq2, serverReq3]); + }); + }); + + context('when SRA mode is not enabled', function() { + it('builds multiple requests, one corresponding to each Ad Unit', function() { + const bidRequests = new BidRequestsBuilder() + .addBid() + .addBid({ + bidId: 'b3', + adUnitCode: 'div-id', + params: { + siteId: 'sample33xGUID123456780', + productId: 'siab' + } + }) + .withBanner() + .withVideo({context: 'outstream'}) + .build(); + + const req1 = new TtxRequestBuilder() + .withProduct('siab') + .withBanner() + .withVideo() + .build(); + + const req2 = new TtxRequestBuilder() + .withProduct('siab') + .withBanner() + .withVideo() + .build(); + + req2.imp[0].id = 'b2'; + + const req3 = new TtxRequestBuilder('sample33xGUID123456780') + .withProduct('siab') + .withBanner() + .withVideo() + .build(); + + req3.imp[0].id = 'b3'; + + const serverReq1 = new ServerRequestBuilder() + .withData(req1) + .build(); + + const serverReq2 = new ServerRequestBuilder() + .withData(req2) + .build(); + + const serverReq3 = new ServerRequestBuilder() + .withData(req3) + .withUrl('https://ssc.33across.com/api/v1/hb?guid=sample33xGUID123456780') + .build(); + + const builtServerRequests = spec.buildRequests(bidRequests, {}); + + expect(builtServerRequests) + .to.deep.equal([ + serverReq1, + serverReq2, + serverReq3 + ]); + }); + }); }); describe('interpretResponse', function() { let ttxRequest, serverRequest; + const videoBid = ''; beforeEach(function() { ttxRequest = new TtxRequestBuilder() @@ -1390,6 +1571,7 @@ describe('33acrossBidAdapter:', function () { page: 'https://test-url.com' }) .build(); + serverRequest = new ServerRequestBuilder() .withUrl('https://staging-ssc.33across.com/api/v1/hb') .withData(ttxRequest) @@ -1405,11 +1587,12 @@ describe('33acrossBidAdapter:', function () { const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [ { bid: [{ id: '1', + impid: 'b1', adm: '

I am an ad

', crid: 1, h: 250, @@ -1441,15 +1624,15 @@ describe('33acrossBidAdapter:', function () { }); it('interprets and returns the single video bid response', function() { - const videoBid = ''; const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [ { bid: [{ id: '1', + impid: 'b1', adm: videoBid, ext: { ttx: { @@ -1497,6 +1680,7 @@ describe('33acrossBidAdapter:', function () { { bid: [{ id: '1', + impid: 'b1', adm: '

I am an ad

', crid: 1, h: 250, @@ -1533,7 +1717,7 @@ describe('33acrossBidAdapter:', function () { const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [] }; @@ -1542,15 +1726,16 @@ describe('33acrossBidAdapter:', function () { }); context('when more than one bids are returned', function() { - it('interprets and returns the the first bid of the first seatbid', function() { + it('interprets and returns all bids', function() { const serverResponse = { cur: 'USD', ext: {}, - id: 'b1', + id: 'r1', seatbid: [ { bid: [{ id: '1', + impid: 'b1', adm: '

I am an ad

', crid: 1, h: 250, @@ -1559,6 +1744,7 @@ describe('33acrossBidAdapter:', function () { }, { id: '2', + impid: 'b2', adm: '

I am an ad

', crid: 2, h: 250, @@ -1570,7 +1756,14 @@ describe('33acrossBidAdapter:', function () { { bid: [{ id: '3', - adm: '

I am an ad

', + impid: 'b3', + adm: videoBid, + ext: { + ttx: { + mediaType: 'video', + vastType: 'xml' + } + }, crid: 3, h: 250, w: 300, @@ -1579,21 +1772,50 @@ describe('33acrossBidAdapter:', function () { } ] }; - const bidResponse = { - requestId: 'b1', - bidderCode: BIDDER_CODE, - cpm: 0.0940, - width: 300, - height: 250, - ad: '

I am an ad

', - ttl: 60, - creativeId: 1, - mediaType: 'banner', - currency: 'USD', - netRevenue: true - }; + const bidResponse = [ + { + requestId: 'b1', + bidderCode: BIDDER_CODE, + cpm: 0.0940, + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 60, + creativeId: 1, + mediaType: 'banner', + currency: 'USD', + netRevenue: true + }, + { + requestId: 'b2', + bidderCode: BIDDER_CODE, + cpm: 0.0938, + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 60, + creativeId: 2, + mediaType: 'banner', + currency: 'USD', + netRevenue: true + }, + { + requestId: 'b3', + bidderCode: BIDDER_CODE, + cpm: 0.0938, + width: 300, + height: 250, + ad: videoBid, + vastXml: '', + ttl: 60, + creativeId: 3, + mediaType: 'video', + currency: 'USD', + netRevenue: true + } + ]; - expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal([bidResponse]); + expect(spec.interpretResponse({ body: serverResponse }, serverRequest)).to.deep.equal(bidResponse); }); }); }); diff --git a/test/spec/modules/adbookpspBidAdapter_spec.js b/test/spec/modules/adbookpspBidAdapter_spec.js index a6b8a794eeb..3a49f25edb6 100755 --- a/test/spec/modules/adbookpspBidAdapter_spec.js +++ b/test/spec/modules/adbookpspBidAdapter_spec.js @@ -504,9 +504,11 @@ describe('adbookpsp bid adapter', () => { ad: '
ad
', adId: '5', adserverTargeting: { + hb_ad_ord_adbookpsp: '0_0', // the value to the left of the underscore represents the index of the ad id and the number to the right represents the order index hb_adid_c_adbookpsp: '5', hb_deal_adbookpsp: 'werwetwerw', hb_liid_adbookpsp: '2342345', + hb_ordid_adbookpsp: '567843', }, referrer: 'http://prebid-test-page.io:8080/banner.html', lineItemId: '2342345', @@ -516,9 +518,11 @@ describe('adbookpsp bid adapter', () => { adId: '10', adUnitCode: 'div-gpt-ad-837465923534-0', adserverTargeting: { + hb_ad_ord_adbookpsp: '0_0', hb_adid_c_adbookpsp: '10', hb_deal_adbookpsp: 'dsfxcxcvxc', hb_liid_adbookpsp: '2121221', + hb_ordid_adbookpsp: '5678234', }, bidId: 'bid4321', bidderRequestId: '999ccceeee11', @@ -556,14 +560,18 @@ describe('adbookpsp bid adapter', () => { expect(bids).to.have.length(2); expect(bids[0].adserverTargeting).to.deep.equal({ + hb_ad_ord_adbookpsp: '0_0', + hb_adid_c_adbookpsp: '5', hb_deal_adbookpsp: 'werwetwerw', hb_liid_adbookpsp: '2342345', - hb_adid_c_adbookpsp: '5', + hb_ordid_adbookpsp: '567843', }); expect(bids[1].adserverTargeting).to.deep.equal({ + hb_ad_ord_adbookpsp: '0_0', + hb_adid_c_adbookpsp: '10', hb_deal_adbookpsp: 'dsfxcxcvxc', hb_liid_adbookpsp: '2121221', - hb_adid_c_adbookpsp: '10', + hb_ordid_adbookpsp: '5678234', }); }); @@ -580,9 +588,11 @@ describe('adbookpsp bid adapter', () => { expect(bids).to.have.length(2); for (const bid of bids) { expect(bid.adserverTargeting).to.deep.equal({ + hb_ad_ord_adbookpsp: '0_0,1_0', + hb_adid_c_adbookpsp: '5,10', hb_deal_adbookpsp: 'werwetwerw,dsfxcxcvxc', hb_liid_adbookpsp: '2342345,2121221', - hb_adid_c_adbookpsp: '5,10', + hb_ordid_adbookpsp: '567843,5678234', }); } }); @@ -670,9 +680,11 @@ describe('adbookpsp bid adapter', () => { ); expect(bids[0].adserverTargeting).to.deep.equal({ + hb_ad_ord_adbookpsp: '0_0', hb_adid_c_adbookpsp: '10', hb_deal_adbookpsp: 'dsfxcxcvxc', hb_liid_adbookpsp: '2121221', + hb_ordid_adbookpsp: '5678234', }); }); @@ -1279,6 +1291,7 @@ const exchangeResponse = { nurl: 'http://win.example.url', ext: { liid: '2342345', + ordid: '567843', }, cat: ['IAB2-1', 'IAB2-2', 'IAB2-3'], adomain: ['advertiser.com'], @@ -1301,6 +1314,7 @@ const exchangeResponse = { nurl: 'http://win.example.url', ext: { liid: '2121221', + ordid: '5678234', }, cat: ['IAB2-3'], adomain: ['advertiser.com', 'campaign.advertiser.com'], diff --git a/test/spec/modules/adhashBidAdapter_spec.js b/test/spec/modules/adhashBidAdapter_spec.js index eda164da852..6c214b84928 100644 --- a/test/spec/modules/adhashBidAdapter_spec.js +++ b/test/spec/modules/adhashBidAdapter_spec.js @@ -77,7 +77,7 @@ describe('adhashBidAdapter', function () { ); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://bidder.adhash.org/rtb?version=1.0&prebid=true'); + expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=1.0&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); expect(result[0].bidRequest).to.equal(bidRequest); expect(result[0].data).to.have.property('timezone'); expect(result[0].data).to.have.property('location'); @@ -93,7 +93,7 @@ describe('adhashBidAdapter', function () { const result = spec.buildRequests([ bidRequest ], { gdprConsent: true }); expect(result.length).to.equal(1); expect(result[0].method).to.equal('POST'); - expect(result[0].url).to.equal('https://bidder.adhash.org/rtb?version=1.0&prebid=true'); + expect(result[0].url).to.equal('https://bidder.adhash.com/rtb?version=1.0&prebid=true&publisher=0xc3b09b27e9c6ef73957901aa729b9e69e5bbfbfb'); expect(result[0].bidRequest).to.equal(bidRequest); expect(result[0].data).to.have.property('timezone'); expect(result[0].data).to.have.property('location'); diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index 78ca25d6be2..3fe0a62b2a0 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -1,5 +1,6 @@ import {expect} from 'chai'; import {spec} from 'modules/adheseBidAdapter.js'; +import {config} from 'src/config.js'; const BID_ID = 456; const TTL = 360; @@ -131,12 +132,21 @@ describe('AdheseAdapter', function () { expect(JSON.parse(req.data)).to.not.have.key('eids'); }); - it('should request vast content as url', function () { + it('should request vast content as url by default', function () { let req = spec.buildRequests([ minimalBid() ], bidderRequest); expect(JSON.parse(req.data).vastContentAsUrl).to.equal(true); }); + it('should request vast content as markup when configured', function () { + sinon.stub(config, 'getConfig').withArgs('adhese').returns({ vastContentAsUrl: false }); + + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).vastContentAsUrl).to.equal(false); + config.getConfig.restore(); + }); + it('should include bids', function () { let bid = minimalBid(); let req = spec.buildRequests([ bid ], bidderRequest); @@ -155,6 +165,22 @@ describe('AdheseAdapter', function () { expect(req.url).to.equal('https://ads-demo.adhese.com/json'); }); + + it('should include params specified in the config', function () { + sinon.stub(config, 'getConfig').withArgs('adhese').returns({ globalTargets: { 'tl': [ 'all' ] } }); + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).parameters).to.deep.include({ 'tl': [ 'all' ] }); + config.getConfig.restore(); + }); + + it('should give priority to bid params over config params', function () { + sinon.stub(config, 'getConfig').withArgs('adhese').returns({ globalTargets: { 'xt': ['CONFIG_CONSENT_STRING'] } }); + let req = spec.buildRequests([ minimalBid() ], bidderRequest); + + expect(JSON.parse(req.data).parameters).to.deep.include({ 'xt': [ 'CONSENT_STRING' ] }); + config.getConfig.restore(); + }); }); describe('interpretResponse', () => { diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js index 01ab3afcf2f..b87fd20adc2 100644 --- a/test/spec/modules/adlooxAnalyticsAdapter_spec.js +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -3,7 +3,7 @@ import analyticsAdapter, { command as analyticsCommand, COMMAND } from 'modules/ import { AUCTION_COMPLETED } from 'src/auction.js'; import { expect } from 'chai'; import events from 'src/events.js'; -import { EVENTS } from 'src/constants.json'; +import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; @@ -143,10 +143,10 @@ describe('Adloox Analytics Adapter', function () { return arg.tagName === 'LINK' && arg.getAttribute('rel') === 'preload' && arg.getAttribute('as') === 'script' && href_uri.href === uri.href; }; - events.emit(EVENTS.AUCTION_END, auctionDetails); + events.emit(CONSTANTS.EVENTS.AUCTION_END, auctionDetails); expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.true; - events.emit(EVENTS.AUCTION_END, auctionDetails); + events.emit(CONSTANTS.EVENTS.AUCTION_END, auctionDetails); expect(insertElementStub.callCount).to.equal(1); done(); @@ -167,7 +167,7 @@ describe('Adloox Analytics Adapter', function () { const querySelectorStub = sandbox.stub(document, 'querySelector'); querySelectorStub.withArgs(`#${bid.adUnitCode}`).returns(slot); - events.emit(EVENTS.BID_WON, bid); + events.emit(CONSTANTS.EVENTS.BID_WON, bid); const [urlInserted, moduleCode] = loadExternalScriptStub.getCall(0).args; @@ -196,7 +196,7 @@ describe('Adloox Analytics Adapter', function () { const querySelectorStub = sandbox.stub(document, 'querySelector'); querySelectorStub.withArgs(`#${bid.adUnitCode}`).returns(slot); - events.emit(EVENTS.BID_WON, bidIgnore); + events.emit(CONSTANTS.EVENTS.BID_WON, bidIgnore); expect(parent.querySelector('script')).is.null; @@ -238,7 +238,7 @@ describe('Adloox Analytics Adapter', function () { it('should inject tracking event', function (done) { const data = { - eventType: EVENTS.BID_WON, + eventType: CONSTANTS.EVENTS.BID_WON, args: bid }; diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index fd467f074ac..33e6a8a89b9 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -175,13 +175,13 @@ describe('AdmanAdapter', function () { }); describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs(); + let userSync = spec.getUserSyncs({}); it('Returns valid URL and type', function () { expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.exist; expect(userSync[0].url).to.exist; expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://pub.admanmedia.com/?c=o&m=sync'); + expect(userSync[0].url).to.be.equal('https://pub.admanmedia.com/image?pbjs=1&coppa=0'); }); }); }); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 20dbaad1cc6..3b6ad8d0912 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -19,6 +19,7 @@ describe('adnuntiusBidAdapter', function () { }); const tzo = new Date().getTimezoneOffset(); const ENDPOINT_URL = `${URL}${tzo}&format=json&userId=${usi}`; + const ENDPOINT_URL_NOCOOKIE = `${URL}${tzo}&format=json&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${URL}${tzo}&format=json&segments=segment1,segment2,segment3&userId=${usi}`; const ENDPOINT_URL_CONSENT = `${URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; const adapter = newBidder(spec); @@ -251,6 +252,24 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); + + it('should user user ID if present in ortb2.user.id field', function () { + config.setBidderConfig({ + bidders: ['adnuntius', 'other'], + config: { + ortb2: { + user: { + id: usi + } + } + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL); + }); }); describe('user privacy', function () { @@ -269,6 +288,22 @@ describe('adnuntiusBidAdapter', function () { }); }); + describe('use cookie', function () { + it('should send noCookie in url if set to false.', function () { + config.setBidderConfig({ + bidders: ['adnuntius'], + config: { + useCookie: false + } + }); + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidRequests)); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL_NOCOOKIE); + }); + }); + describe('interpretResponse', function () { it('should return valid response when passed valid server response', function () { const interpretedResponse = spec.interpretResponse(serverResponse, singleBidRequest); diff --git a/test/spec/modules/adomikAnalyticsAdapter_spec.js b/test/spec/modules/adomikAnalyticsAdapter_spec.js index 1414b2402b9..93822d04c88 100644 --- a/test/spec/modules/adomikAnalyticsAdapter_spec.js +++ b/test/spec/modules/adomikAnalyticsAdapter_spec.js @@ -1,5 +1,6 @@ import adomikAnalytics from 'modules/adomikAnalyticsAdapter.js'; -import {expect} from 'chai'; +import { expect } from 'chai'; + let events = require('src/events'); let adapterManager = require('src/adapterManager').default; let constants = require('src/constants.json'); @@ -8,6 +9,7 @@ describe('Adomik Prebid Analytic', function () { let sendEventStub; let sendWonEventStub; let clock; + before(function () { clock = sinon.useFakeTimers(); }); @@ -43,6 +45,8 @@ describe('Adomik Prebid Analytic', function () { const initOptions = { id: '123456', url: 'testurl', + testId: '12345', + testValue: '1000' }; const bid = { @@ -69,6 +73,9 @@ describe('Adomik Prebid Analytic', function () { expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', url: 'testurl', + sampling: undefined, + testId: '12345', + testValue: '1000', id: '', timeouted: false }); @@ -79,6 +86,9 @@ describe('Adomik Prebid Analytic', function () { expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', url: 'testurl', + sampling: undefined, + testId: '12345', + testValue: '1000', id: 'test-test-test', timeouted: false }); @@ -91,7 +101,7 @@ describe('Adomik Prebid Analytic', function () { type: 'request', event: { bidder: 'BIDDERTEST', - placementCode: 'placementtest', + placementCode: '0000', } }); diff --git a/test/spec/modules/adplusBidAdapter_spec.js b/test/spec/modules/adplusBidAdapter_spec.js new file mode 100644 index 00000000000..840d86c80f1 --- /dev/null +++ b/test/spec/modules/adplusBidAdapter_spec.js @@ -0,0 +1,213 @@ +import {expect} from 'chai'; +import {spec, BIDDER_CODE, ADPLUS_ENDPOINT, } from 'modules/adplusBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +describe('AplusBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.be.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + let validRequest = { + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + inventoryId: '30', + adUnitId: '1', + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let validRequest = { + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + inventoryId: '30', + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + + it('should return false when required param types are wrong', function () { + let validRequest = { + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + inventoryId: 30, + adUnitId: '1', + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + + it('should return false when size is not exists', function () { + let validRequest = { + params: { + inventoryId: 30, + adUnitId: '1', + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + + it('should return false when size is wrong', function () { + let validRequest = { + mediaTypes: { + banner: { + sizes: [[300]] + } + }, + params: { + inventoryId: 30, + adUnitId: '1', + } + }; + expect(spec.isBidRequestValid(validRequest)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let validRequest = [ + { + bidder: BIDDER_CODE, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + inventoryId: '-1', + adUnitId: '-3', + }, + bidId: '2bdcb0b203c17d' + }, + ]; + + let bidderRequest = { + refererInfo: { + referer: 'https://test.domain' + } + }; + + it('bidRequest HTTP method', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request[0].method).to.equal('GET'); + }); + + it('bidRequest url', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request[0].url).to.equal(ADPLUS_ENDPOINT); + }); + + it('tests bidRequest data is clean and has the right values', function () { + const request = spec.buildRequests(validRequest, bidderRequest); + + expect(request[0].data.bidId).to.equal('2bdcb0b203c17d'); + expect(request[0].data.inventoryId).to.equal('-1'); + expect(request[0].data.adUnitId).to.equal('-3'); + expect(request[0].data.adUnitWidth).to.equal(300); + expect(request[0].data.adUnitHeight).to.equal(250); + expect(request[0].data.sdkVersion).to.equal('1'); + expect(typeof request[0].data.session).to.equal('string'); + expect(request[0].data.session).length(36); + expect(request[0].data.interstitial).to.equal(0); + expect(request[0].data).to.not.have.deep.property('extraData'); + expect(request[0].data).to.not.have.deep.property('yearOfBirth'); + expect(request[0].data).to.not.have.deep.property('gender'); + expect(request[0].data).to.not.have.deep.property('categories'); + expect(request[0].data).to.not.have.deep.property('latitude'); + expect(request[0].data).to.not.have.deep.property('longitude'); + }); + }); + + describe('interpretResponse', function () { + const requestData = { + language: window.navigator.language, + screenWidth: 1440, + screenHeight: 900, + sdkVersion: '1', + inventoryId: '-1', + adUnitId: '-3', + adUnitWidth: 300, + adUnitHeight: 250, + domain: 'tassandigi.com', + pageUrl: 'https%3A%2F%2Ftassandigi.com%2Fserafettin%2Fads.html', + interstitial: 0, + session: '1c02db03-5289-932a-93af-7b4022611fec', + token: '1c02db03-5289-937a-93df-7b4022611fec', + secure: 1, + bidId: '2bdcb0b203c17d', + }; + const bidRequest = { + 'method': 'GET', + 'url': ADPLUS_ENDPOINT, + 'data': requestData, + }; + + const bidResponse = { + body: [ + { + 'ad': '
ad
', + 'advertiserDomains': [ + 'advertiser.com' + ], + 'categoryIDs': [ + 'IAB-111' + ], + 'cpm': 3.57, + 'creativeID': '1', + 'currency': 'TRY', + 'dealID': '1', + 'height': 300, + 'mediaType': 'banner', + 'netRevenue': true, + 'requestID': '2bdcb0b203c17d', + 'ttl': 300, + 'width': 250 + } + ], + headers: {} + }; + + const emptyBidResponse = { + body: null, + }; + + it('returns an empty array when the result body is not valid', function () { + const result = spec.interpretResponse(emptyBidResponse, bidRequest); + expect(result).to.deep.equal([]); + }); + + it('result is correct', function () { + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result[0].requestId).to.equal('2bdcb0b203c17d'); + expect(result[0].cpm).to.equal(3.57); + expect(result[0].width).to.equal(250); + expect(result[0].height).to.equal(300); + expect(result[0].creativeId).to.equal('1'); + expect(result[0].currency).to.equal('TRY'); + expect(result[0].dealId).to.equal('1'); + expect(result[0].mediaType).to.equal('banner'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + expect(result[0].meta.advertiserDomains).to.deep.equal(['advertiser.com']); + expect(result[0].meta.secondaryCatIds).to.deep.equal(['IAB-111']); + }); + }); +}); diff --git a/test/spec/modules/adqueryIdSystem_spec.js b/test/spec/modules/adqueryIdSystem_spec.js new file mode 100644 index 00000000000..ab98b253b33 --- /dev/null +++ b/test/spec/modules/adqueryIdSystem_spec.js @@ -0,0 +1,74 @@ +import { adqueryIdSubmodule, storage } from 'modules/adqueryIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; +import {amxIdSubmodule} from '../../../modules/amxIdSystem'; +import * as utils from '../../../src/utils'; + +const config = { + storage: { + type: 'html5', + }, +}; + +describe('AdqueryIdSystem', function () { + describe('qid submodule', () => { + it('should expose a "name" property containing qid', () => { + expect(adqueryIdSubmodule.name).to.equal('qid'); + }); + + it('should expose a "gvlid" property containing the GVL ID 902', () => { + expect(adqueryIdSubmodule.gvlid).to.equal(902); + }); + }); + + describe('getId', function() { + let getDataFromLocalStorageStub; + + beforeEach(function() { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + it('gets a adqueryId', function() { + const config = { + params: {} + }; + const callbackSpy = sinon.spy(); + const callback = adqueryIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq(`https://bidder.adquery.io/prebid/qid`); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ qid: 'qid' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({qid: 'qid'}); + }); + + it('gets a cached adqueryId', function() { + const config = { + params: {} + }; + getDataFromLocalStorageStub.withArgs('qid').returns('qid'); + + const callbackSpy = sinon.spy(); + const callback = adqueryIdSubmodule.getId(config).callback; + callback(callbackSpy); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({qid: 'qid'}); + }); + + it('allows configurable id url', function() { + const config = { + params: { + url: 'https://bidder.adquery.io' + } + }; + const callbackSpy = sinon.spy(); + const callback = adqueryIdSubmodule.getId(config).callback; + callback(callbackSpy); + const request = server.requests[0]; + expect(request.url).to.eq('https://bidder.adquery.io'); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ qid: 'testqid' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal({qid: 'testqid'}); + }); + }); +}); diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 950c05edfc5..7721295572c 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -215,7 +215,7 @@ describe('Adxcg adapter', function () { site: { publisher: { id: 4441, - name: 'publishers name' + name: 'publisher\'s name' } } } @@ -227,23 +227,16 @@ describe('Adxcg adapter', function () { let refererInfo = {referer: 'page'}; let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo}).data); - let expected = { + assert.deepEqual(request.site, { + domain: 'localhost', id: '123123', + page: refererInfo.referer, publisher: { domain: 'publisher.domain.com', id: 4441, - name: 'publishers name' - }, - page: 'page', - domain: 'localhost' - }; - - assert.deepEqual(request.site.id, expected.id); - assert.deepEqual(request.site.publisher, expected.publisher); - assert.deepEqual(request.site.page, expected.page); - // assert.deepEqual(request.site.domain, expected.site.domain); // disabled for browser specific problem (IE11) - // in browserstack IE 11 we get empty "" request.site.domain, while in gulp test we get "localhost" - // all other browserstack enabled browsers work without problem + name: 'publisher\'s name' + } + }); }); it('should pass extended ids', function () { diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index befc95e5f24..4a13fbf1232 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -95,6 +95,40 @@ describe('Adyoulike Adapter', function () { } ]; + const bidRequestWithMultipleMediatype = [ + { + 'bidId': 'bid_id_0', + 'bidder': 'adyoulike', + 'placementCode': 'adunit/hb-0', + 'params': { + 'placement': 'placement_0' + }, + 'sizes': '300x250', + 'mediaTypes': { + 'banner': { + 'sizes': ['640x480'] + }, + 'video': { + 'playerSize': [640, 480], + 'context': 'outstream' + }, + 'native': { + 'image': { + 'required': true, + }, + 'title': { + 'required': true, + 'len': 80 + }, + 'cta': { + 'required': false + }, + } + }, + 'transactionId': 'bid_id_0_transaction_id' + } + ]; + const bidRequestWithNativeImageType = [ { 'bidId': 'bid_id_0', @@ -590,6 +624,32 @@ describe('Adyoulike Adapter', function () { expect(payload.gdprConsent.consentRequired).to.be.null; }); + it('should add userid eids information to the request', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'userId': { + pubcid: '01EAJWWNEPN3CYMM5N8M5VXY22', + unsuported: '666' + } + }; + + bidderRequest.bids = bidRequestWithSinglePlacement; + + const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.userId).to.exist; + expect(payload.userId).to.deep.equal([{ + 'source': 'pubcid.org', + 'uids': [{ + 'atype': 1, + 'id': '01EAJWWNEPN3CYMM5N8M5VXY22' + }] + }]); + }); + it('sends bid request to endpoint with single placement', function () { const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); const payload = JSON.parse(request.data); @@ -621,6 +681,21 @@ describe('Adyoulike Adapter', function () { expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); }); + it('sends bid request to endpoint with single placement multiple mediatype', function () { + canonicalQuery.restore(); + const request = spec.buildRequests(bidRequestWithMultipleMediatype, bidderRequest); + const payload = JSON.parse(request.data); + + expect(request.url).to.contain(getEndpoint()); + expect(request.method).to.equal('POST'); + + expect(request.url).to.not.contains('CanonicalUrl=' + encodeURIComponent(canonicalUrl)); + expect(payload.Version).to.equal('1.0'); + expect(payload.Bids['bid_id_0'].PlacementID).to.be.equal('placement_0'); + expect(payload.PageRefreshed).to.equal(false); + expect(payload.Bids['bid_id_0'].TransactionID).to.be.equal('bid_id_0_transaction_id'); + }); + it('sends bid request to endpoint with multiple placements', function () { const request = spec.buildRequests(bidRequestMultiPlacements, bidderRequest); const payload = JSON.parse(request.data); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index fbcce5a1322..e7724a4dc83 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1004,12 +1004,16 @@ describe('AppNexusAdapter', function () { describe('interpretResponse', function () { let bfStub; + let bidderSettingsStorage; + before(function() { bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + bidderSettingsStorage = $$PREBID_GLOBAL$$.bidderSettings; }); after(function() { bfStub.restore(); + $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsStorage; }); let response = { @@ -1077,6 +1081,15 @@ describe('AppNexusAdapter', function () { 'adUnitCode': 'code', 'appnexus': { 'buyerMemberId': 958 + }, + 'meta': { + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [{ + 'bsid': '958' + }] + } } } ]; @@ -1085,11 +1098,46 @@ describe('AppNexusAdapter', function () { bidId: '3db3773286ee59', adUnitCode: 'code' }] - } + }; let result = spec.interpretResponse({ body: response }, {bidderRequest}); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); + it('should reject 0 cpm bids', function () { + let zeroCpmResponse = deepClone(response); + zeroCpmResponse.tags[0].ads[0].cpm = 0; + + let bidderRequest = { + bidderCode: 'appnexus' + }; + + let result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); + expect(result.length).to.equal(0); + }); + + it('should allow 0 cpm bids if allowZeroCpmBids setConfig is true', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + appnexus: { + allowZeroCpmBids: true + } + }; + + let zeroCpmResponse = deepClone(response); + zeroCpmResponse.tags[0].ads[0].cpm = 0; + + let bidderRequest = { + bidderCode: 'appnexus', + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + }; + + let result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); + expect(result.length).to.equal(1); + expect(result[0].cpm).to.equal(0); + }); + it('handles nobid responses', function () { let response = { 'version': '0.0.1', diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index e29994eba44..d9b8cac10b4 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec, VIDEO_ENDPOINT, BANNER_ENDPOINT, OUTSTREAM_SRC, DEFAULT_MIMES } from 'modules/beachfrontBidAdapter.js'; -import { parseUrl } from 'src/utils.js'; +import { config } from 'src/config.js'; +import { parseUrl, deepAccess } from 'src/utils.js'; describe('BeachfrontAdapter', function () { let bidRequests; @@ -556,6 +557,69 @@ describe('BeachfrontAdapter', function () { }); }); + describe('with first-party data', function () { + let sandbox + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('must add first-party data to the video bid request', function () { + sandbox.stub(config, 'getConfig').callsFake(key => { + const cfg = { + ortb2: { + site: { + keywords: 'test keyword' + }, + user: { + data: 'some user data' + } + } + }; + return deepAccess(cfg, key); + }); + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { video: {} }; + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html' + } + }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + expect(data.user.data).to.equal('some user data'); + expect(data.site.keywords).to.equal('test keyword'); + expect(data.site.page).to.equal('http://example.com/page.html'); + expect(data.site.domain).to.equal('example.com'); + }); + + it('must add first-party data to the banner bid request', function () { + sandbox.stub(config, 'getConfig').callsFake(key => { + const cfg = { + ortb2: { + site: { + keywords: 'test keyword' + }, + user: { + data: 'some user data' + } + } + }; + return deepAccess(cfg, key); + }); + const bidRequest = bidRequests[0]; + bidRequest.mediaTypes = { banner: {} }; + const requests = spec.buildRequests([ bidRequest ]); + const data = requests[0].data; + expect(data.ortb2.user.data).to.equal('some user data'); + expect(data.ortb2.site.keywords).to.equal('test keyword'); + }); + }); + describe('for multi-format bids', function () { it('should create a POST request for each bid format', function () { const width = 300; diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index b68adb8f196..832ad2707d3 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -124,7 +124,7 @@ describe('BeOp Bid Adapter tests', () => { expect(payload.tc_string).to.exist; expect(payload.tc_string).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='); expect(payload.url).to.exist; - expect(payload.url).to.equal('http://test.te'); + expect(payload.url).to.equal('http://localhost:9876/context.html'); }); }); diff --git a/test/spec/modules/bidViewabilityIO_spec.js b/test/spec/modules/bidViewabilityIO_spec.js index b59dbc867c1..5b4944082bc 100644 --- a/test/spec/modules/bidViewabilityIO_spec.js +++ b/test/spec/modules/bidViewabilityIO_spec.js @@ -3,7 +3,7 @@ import * as events from 'src/events.js'; import * as utils from 'src/utils.js'; import * as sinon from 'sinon'; import { expect } from 'chai'; -import { EVENTS } from 'src/constants.json'; +import CONSTANTS from 'src/constants.json'; describe('#bidViewabilityIO', function() { const makeElement = (id) => { @@ -97,7 +97,7 @@ describe('#bidViewabilityIO', function() { expect(mockObserver.unobserve.calledOnce).to.be.true; expect(emitSpy.calledOnce).to.be.true; // expect(emitSpy.firstCall.args).to.be.false; - expect(emitSpy.firstCall.args[0]).to.eq(EVENTS.BID_VIEWABLE); + expect(emitSpy.firstCall.args[0]).to.eq(CONSTANTS.EVENTS.BID_VIEWABLE); }); }) diff --git a/test/spec/modules/bidViewability_spec.js b/test/spec/modules/bidViewability_spec.js index 211dec090a5..a822d86f852 100644 --- a/test/spec/modules/bidViewability_spec.js +++ b/test/spec/modules/bidViewability_spec.js @@ -5,7 +5,7 @@ import * as utils from 'src/utils.js'; import * as sinon from 'sinon'; import {expect, spy} from 'chai'; import * as prebidGlobal from 'src/prebidGlobal.js'; -import { EVENTS } from 'src/constants.json'; +import CONSTANTS from 'src/constants.json'; import adapterManager, { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; import parse from 'url-parse'; @@ -279,9 +279,9 @@ describe('#bidViewability', function() { let call = callBidViewableBidderSpy.getCall(0); expect(call.args[0]).to.equal(PBJS_WINNING_BID.bidder); expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); - // EVENTS.BID_VIEWABLE is triggered + // CONSTANTS.EVENTS.BID_VIEWABLE is triggered call = eventsEmitSpy.getCall(0); - expect(call.args[0]).to.equal(EVENTS.BID_VIEWABLE); + expect(call.args[0]).to.equal(CONSTANTS.EVENTS.BID_VIEWABLE); expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); }); @@ -290,7 +290,7 @@ describe('#bidViewability', function() { expect(triggerPixelSpy.callCount).to.equal(0); // adapterManager.callBidViewableBidder is NOT called expect(callBidViewableBidderSpy.callCount).to.equal(0); - // EVENTS.BID_VIEWABLE is NOT triggered + // CONSTANTS.EVENTS.BID_VIEWABLE is NOT triggered expect(eventsEmitSpy.callCount).to.equal(0); }); }); diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index 04a200d95a7..729605f7db8 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -477,6 +477,8 @@ const testsBuildRequests = [ refererInfo: getConfigBuildRequest('banner').refererInfo }, data: { + gdpr: false, + gdpr_consent: '', height: 250, width: 300, keywords: '', diff --git a/test/spec/modules/codefuelBidAdapter_spec.js b/test/spec/modules/codefuelBidAdapter_spec.js index 808c221af07..a2549012d84 100644 --- a/test/spec/modules/codefuelBidAdapter_spec.js +++ b/test/spec/modules/codefuelBidAdapter_spec.js @@ -183,7 +183,7 @@ describe('Codefuel Adapter', function () { tmax: 500 } const res = spec.buildRequests([bidRequest], commonBidderRequest) - expect(res.url).to.equal('https://bidder-url.com') + expect(res.url).to.equal('https://ai-p-codefuel-ds-rtb-us-east-1-k8s.seccint.com/prebid') expect(res.data).to.deep.equal(expectedData) }) diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index 0fe4d5b358e..91f900d6279 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -7,7 +7,8 @@ describe('ColossussspAdapter', function () { bidder: 'colossusssp', bidderRequestId: '145e1d6a7837c9', params: { - placement_id: 0 + placement_id: 0, + group_id: 0 }, placementCode: 'placementid_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', @@ -60,6 +61,7 @@ describe('ColossussspAdapter', function () { }); it('Should return false when placement_id is not a number', function () { bid.params.placement_id = 'aaa'; + delete bid.params.group_id; expect(spec.isBidRequestValid(bid)).to.be.false; }); }); @@ -95,9 +97,10 @@ describe('ColossussspAdapter', function () { let placements = data['placements']; for (let i = 0; i < placements.length; i++) { let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'floor', 'gpid'); + expect(placement).to.have.all.keys('placementId', 'groupId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'floor', 'gpid'); expect(placement.schain).to.be.an('object') expect(placement.placementId).to.be.a('number'); + expect(placement.groupId).to.be.a('number'); expect(placement.bidId).to.be.a('string'); expect(placement.traffic).to.be.a('string'); expect(placement.sizes).to.be.an('array'); @@ -186,6 +189,15 @@ describe('ColossussspAdapter', function () { }); }); + describe('onBidWon', function () { + it('should make an ajax call', function () { + const bid = { + nurl: 'http://example.com/win', + }; + expect(spec.onBidWon(bid)).to.equals(undefined); + }); + }) + describe('getUserSyncs', function () { let userSync = spec.getUserSyncs(); it('Returns valid URL and type', function () { diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js new file mode 100644 index 00000000000..28021c4f7c0 --- /dev/null +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -0,0 +1,398 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/compassBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'compass' + +describe('CompassBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://sa-lb.deliverimp.com/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sa-cs.deliverimp.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sa-cs.deliverimp.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 5e9b0f07f46..d7ae3c58a85 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -715,6 +715,26 @@ describe('consentManagement', function () { expect(consent).to.be.null; }); + it('allows the auction when CMP is unresponsive', (done) => { + setConsentConfig({ + cmpApi: 'iab', + timeout: 10, + defaultGdprScope: true + }); + + requestBidsHook(() => { + didHookReturn = true; + }, {}); + + setTimeout(() => { + expect(didHookReturn).to.be.true; + const consent = gdprDataHandler.getConsentData(); + expect(consent.gdprApplies).to.be.true; + expect(consent.consentString).to.be.undefined; + done(); + }, 20); + }); + it('It still considers it a valid cmp response if gdprApplies is not a boolean', function () { // gdprApplies is undefined, should just still store consent response but use whatever defaultGdprScope was let testConsentData = { diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 828b8401af1..d50eadebb55 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -116,16 +116,19 @@ describe('CriteoId module', function () { expect(setCookieStub.calledWith('cto_bundle')).to.be.false; expect(setLocalStorageStub.calledWith('cto_bundle')).to.be.false; } else if (response.bundle) { - expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs)).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.testdev.com')).to.be.true; expect(setLocalStorageStub.calledWith('cto_bundle', response.bundle)).to.be.true; expect(triggerPixelStub.called).to.be.false; } if (response.bidId) { - expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs)).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.testdev.com')).to.be.true; expect(setLocalStorageStub.calledWith('cto_bidid', response.bidId)).to.be.true; } else { - expect(setCookieStub.calledWith('cto_bidid', '', pastDateString)).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', '', pastDateString, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', '', pastDateString, null, '.testdev.com')).to.be.true; expect(removeFromLocalStorageStub.calledWith('cto_bidid')).to.be.true; } }); diff --git a/test/spec/modules/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js index 4ed19cf7b74..3f922fc1471 100644 --- a/test/spec/modules/cwireBidAdapter_spec.js +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -36,11 +36,6 @@ const BidderRequestBuilder = function BidderRequestBuilder(options) { bidderRequestId: BID_DEFAULTS.request.bidderRequestId, transactionId: BID_DEFAULTS.request.transactionId, timeout: 3000, - refererInfo: { - numIframes: 0, - reachedTop: true, - referer: 'http://test.io/index.html?pbjs_debug=true' - } }; const request = { @@ -82,16 +77,13 @@ const BidRequestBuilder = function BidRequestBuilder(options, deleteKeys) { }; describe('C-WIRE bid adapter', () => { - let utilsMock; let sandbox; beforeEach(() => { - utilsMock = sinon.mock(utils); sandbox = sinon.createSandbox(); }); afterEach(() => { - utilsMock.restore(); sandbox.restore(); }); @@ -146,6 +138,12 @@ describe('C-WIRE bid adapter', () => { describe('C-WIRE - buildRequests()', function () { it('creates a valid request', function () { + // for whatever reason stub for getWindowLocation does not work + // so this was the closest way to test for get params + const params = sandbox.stub(utils, 'getParameterByName'); + params.withArgs('cwgroups').returns('group_1'); + params.withArgs('cwcreative').returns('54321'); + const bid01 = new BidRequestBuilder({ mediaTypes: { banner: { @@ -153,12 +151,16 @@ describe('C-WIRE bid adapter', () => { } } }).withParams().build(); + const bidderRequest01 = new BidderRequestBuilder().build(); const requests = spec.buildRequests([bid01], bidderRequest01); + expect(requests.data.slots.length).to.equal(1); expect(requests.data.cwid).to.be.null; expect(requests.data.slots[0].sizes[0]).to.equal('1x1'); + expect(requests.data.cwcreative).to.equal('54321'); + expect(requests.data.refgroups[0]).to.equal('group_1'); }); }); diff --git a/test/spec/modules/dailyhuntBidAdapter_spec.js b/test/spec/modules/dailyhuntBidAdapter_spec.js new file mode 100644 index 00000000000..f347d6cec5b --- /dev/null +++ b/test/spec/modules/dailyhuntBidAdapter_spec.js @@ -0,0 +1,404 @@ +import { expect } from 'chai'; +import { spec } from 'modules/dailyhuntBidAdapter.js'; + +const PROD_PREBID_ENDPOINT_URL = 'https://pbs.dailyhunt.in/openrtb2/auction?partner=dailyhunt'; +const PROD_PREBID_TEST_ENDPOINT_URL = 'https://qa-pbs-van.dailyhunt.in/openrtb2/auction?partner=dailyhunt'; + +const _encodeURIComponent = function (a) { + if (!a) { return } + let b = window.encodeURIComponent(a); + b = b.replace(/'/g, '%27'); + return b; +} + +describe('DailyhuntAdapter', function () { + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'dailyhunt', + 'params': { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt' + } + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + describe('buildRequests', function() { + let bidRequests = [ + { + bidder: 'dailyhunt', + params: { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt', + bidfloor: 0.1, + device: { + ip: '47.9.247.217' + }, + site: { + cat: ['1', '2', '3'] + } + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 50]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + let nativeBidRequests = [ + { + bidder: 'dailyhunt', + params: { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt', + }, + nativeParams: { + title: { + required: true, + len: 80 + }, + image: { + required: true, + sizes: [150, 50] + }, + }, + mediaTypes: { + native: { + title: { + required: true + }, + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 50]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + let videoBidRequests = [ + { + bidder: 'dailyhunt', + params: { + placement_id: 1, + publisher_id: 1, + partner_name: 'dailyhunt' + }, + nativeParams: { + video: { + context: 'instream' + } + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[300, 250], [300, 50]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + let bidderRequest = { + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'bidderCode': 'dailyhunt', + 'bids': [ + { + ...bidRequests[0] + } + ], + 'refererInfo': { + 'referer': 'http://m.dailyhunt.in/' + } + }; + let nativeBidderRequest = { + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'bidderCode': 'dailyhunt', + 'bids': [ + { + ...nativeBidRequests[0] + } + ], + 'refererInfo': { + 'referer': 'http://m.dailyhunt.in/' + } + }; + let videoBidderRequest = { + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'bidderCode': 'dailyhunt', + 'bids': [ + { + ...videoBidRequests[0] + } + ], + 'refererInfo': { + 'referer': 'http://m.dailyhunt.in/' + } + }; + + it('sends display bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); + expect(request.method).to.equal('POST'); + }); + + it('sends native bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(nativeBidRequests, nativeBidderRequest)[0]; + expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); + expect(request.method).to.equal('POST'); + }); + + it('sends video bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(videoBidRequests, videoBidderRequest)[0]; + expect(request.url).to.equal(PROD_PREBID_ENDPOINT_URL); + expect(request.method).to.equal('POST'); + }); + }); + describe('interpretResponse', function () { + let bidResponses = { + id: 'da32def7-6779-403c-ada7-0b201dbc9744', + seatbid: [ + { + bid: [ + { + id: 'id1', + impid: 'banner-impid', + price: 1.4, + adm: 'adm', + adid: '66658', + crid: 'asd5ddbf014cac993.66466212', + dealid: 'asd5ddbf014cac993.66466212', + w: 300, + h: 250, + nurl: 'winUrl', + ext: { + prebid: { + type: 'banner' + } + } + }, + { + id: '5caccc1f-94a6-4230-a1f9-6186ee65da99', + impid: 'video-impid', + price: 1.4, + nurl: 'winUrl', + adm: 'adm', + adid: '980', + crid: '2394', + w: 300, + h: 250, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + cacheKey: 'cache_key', + vastUrl: 'vastUrl' + } + } + }, + { + id: '74973faf-cce7-4eff-abd0-b59b8e91ca87', + impid: 'native-impid', + price: 50, + nurl: 'winUrl', + adm: '{"native":{"link":{"url":"url","clicktrackers":[]},"assets":[{"id":1,"required":1,"img":{},"video":{},"data":{},"title":{"text":"TITLE"},"link":{}},{"id":1,"required":1,"img":{},"video":{},"data":{"type":2,"value":"Lorem Ipsum Lorem Ipsum Lorem Ipsum."},"title":{},"link":{}},{"id":1,"required":1,"img":{},"video":{},"data":{"type":12,"value":"Install Here"},"title":{},"link":{}},{"id":1,"required":1,"img":{"type":3,"url":"urk","w":990,"h":505},"video":{},"data":{},"title":{},"link":{}}],"imptrackers":[]}}', + adid: '968', + crid: '2370', + w: 300, + h: 250, + ext: { + prebid: { + type: 'native' + }, + bidder: null + } + }, + { + id: '5caccc1f-94a6-4230-a1f9-6186ee65da99', + impid: 'video-outstream-impid', + price: 1.4, + nurl: 'winUrl', + adm: 'adm', + adid: '980', + crid: '2394', + w: 300, + h: 250, + ext: { + prebid: { + 'type': 'video' + }, + bidder: { + cacheKey: 'cache_key', + vastUrl: 'vastUrl' + } + } + }, + ], + seat: 'dailyhunt' + } + ], + ext: { + responsetimemillis: { + dailyhunt: 119 + } + } + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + requestId: '1', + cpm: 1.4, + creativeId: 'asd5ddbf014cac993.66466212', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + ad: 'adm', + mediaType: 'banner', + winUrl: 'winUrl', + adomain: 'dailyhunt' + }, + { + requestId: '2', + cpm: 1.4, + creativeId: '2394', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + mediaType: 'video', + winUrl: 'winUrl', + adomain: 'dailyhunt', + videoCacheKey: 'cache_key', + vastUrl: 'vastUrl', + }, + { + requestId: '3', + cpm: 1.4, + creativeId: '2370', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + mediaType: 'native', + winUrl: 'winUrl', + adomain: 'dailyhunt', + native: { + clickUrl: 'https%3A%2F%2Fmontu1996.github.io%2F', + clickTrackers: [], + impressionTrackers: [], + javascriptTrackers: [], + title: 'TITLE', + body: 'Lorem Ipsum Lorem Ipsum Lorem Ipsum.', + cta: 'Install Here', + image: { + url: 'url', + height: 505, + width: 990 + } + } + }, + { + requestId: '4', + cpm: 1.4, + creativeId: '2394', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + mediaType: 'video', + winUrl: 'winUrl', + adomain: 'dailyhunt', + vastXml: 'adm', + }, + ]; + let bidderRequest = { + bids: [ + { + bidId: 'banner-impid', + adUnitCode: 'code1', + requestId: '1' + }, + { + bidId: 'video-impid', + adUnitCode: 'code2', + requestId: '2', + mediaTypes: { + video: { + context: 'instream' + } + } + }, + { + bidId: 'native-impid', + adUnitCode: 'code3', + requestId: '3' + }, + { + bidId: 'video-outstream-impid', + adUnitCode: 'code4', + requestId: '4', + mediaTypes: { + video: { + context: 'outstream' + } + } + }, + ] + } + let result = spec.interpretResponse({ body: bidResponses }, bidderRequest); + result.forEach((r, i) => { + expect(Object.keys(r)).to.have.members(Object.keys(expectedResponse[i])); + }); + }); + }) + describe('onBidWon', function () { + it('should hit win url when bid won', function () { + let bid = { + requestId: '1', + cpm: 1.4, + creativeId: 'asd5ddbf014cac993.66466212', + width: 300, + height: 250, + ttl: 360, + netRevenue: true, + currency: 'USD', + ad: 'adm', + mediaType: 'banner', + winUrl: 'winUrl' + }; + expect(spec.onBidWon(bid)).to.equal(undefined); + }); + }) +}) diff --git a/test/spec/modules/datablocksBidAdapter_spec.js b/test/spec/modules/datablocksBidAdapter_spec.js index 0ec12905430..ff7b0aad48c 100644 --- a/test/spec/modules/datablocksBidAdapter_spec.js +++ b/test/spec/modules/datablocksBidAdapter_spec.js @@ -96,8 +96,8 @@ const bidderRequest = { refererInfo: { numIframes: 0, reachedTop: true, - referer: 'https://v5demo.datablocks.net/test', - stack: ['https://v5demo.datablocks.net/test'] + referer: 'https://7560.v5demo.datablocks.net/test', + stack: ['https://7560.v5demo.datablocks.net/test'] }, start: Date.now(), timeout: 10000 @@ -452,7 +452,7 @@ describe('DatablocksAdapter', function() { it('Returns valid URL', function() { expect(request.url).to.exist; - expect(request.url).to.equal('https://7560.v5demo.datablocks.net/openrtb/?sid=7560'); + expect(request.url).to.equal('https://v5demo.datablocks.net/openrtb/?sid=7560'); }); it('Creates an array of request objects', function() { diff --git a/test/spec/modules/dchain_spec.js b/test/spec/modules/dchain_spec.js new file mode 100644 index 00000000000..45061c539c1 --- /dev/null +++ b/test/spec/modules/dchain_spec.js @@ -0,0 +1,329 @@ +import { checkDchainSyntax, addBidResponseHook } from '../../../modules/dchain.js'; +import { config } from '../../../src/config.js'; +import { expect } from 'chai'; + +describe('dchain module', function () { + const STRICT = 'strict'; + const RELAX = 'relaxed'; + const OFF = 'off'; + + describe('checkDchainSyntax', function () { + let bid; + + beforeEach(function () { + bid = { + meta: { + dchain: { + 'ver': '1.0', + 'complete': 0, + 'ext': {}, + 'nodes': [{ + 'asi': 'domain.com', + 'bsid': '12345', + }, { + 'name': 'bidder', + 'domain': 'bidder.com', + 'ext': {} + }] + } + } + }; + }); + + it('Returns false if complete param is not 0 or 1', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.complete = 0; // integer + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.complete = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.complete = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.complete; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.complete = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if ver param is not a String', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.ver = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.ver = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.ver; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ver = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if ext param is not an Object', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.ext = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.true; + delete dchainConfig.ext; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.ext = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.ext = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes param is not an Array', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes = 1; // integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = '1'; // string + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if unknown field is used in main dchain', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.test = '1'; // String + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].asi is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].asi = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].asi = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].asi = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].asi; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].asi = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].asi = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].bsid is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].bsid = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].bsid = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].bsid = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].bsid; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].bsid = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].bsid = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].rid is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].rid = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].rid = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].rid = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].rid; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].rid = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].rid = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].name is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].name = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].name = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].name = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].name; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].name = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].name = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].domain is not a String', function () { + let dchainConfig = bid.meta.dchain; + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].domain = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].domain = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].domain = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.false; + delete dchainConfig.nodes[0].domain; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].domain = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].domain = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if nodes[].ext is not an Object', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.nodes[0].ext = '1'; // String + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = 1; // Integer + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = 1.1; // float + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = {}; // object + expect(checkDchainSyntax(bid, STRICT)).to.true; + delete dchainConfig.nodes[0].ext; // undefined + expect(checkDchainSyntax(bid, STRICT)).to.true; + dchainConfig.nodes[0].ext = true; // boolean + expect(checkDchainSyntax(bid, STRICT)).to.false; + dchainConfig.nodes[0].ext = []; // array + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Returns false if unknown field is used in nodes[]', function () { + let dchainConfig = bid.meta.dchain; + dchainConfig.nodes[0].test = '1'; // String + expect(checkDchainSyntax(bid, STRICT)).to.false; + }); + + it('Relaxed mode: returns true even for invalid config', function () { + bid.meta.dchain = { + ver: 1.1, + complete: '0', + nodes: [{ + name: 'asdf', + domain: ['domain.com'] + }] + }; + + expect(checkDchainSyntax(bid, RELAX)).to.true; + }); + }); + + describe('addBidResponseHook', function () { + let bid; + let adUnitCode = 'adUnit1'; + + beforeEach(function () { + bid = { + bidderCode: 'bidderA', + meta: { + dchain: { + 'ver': '1.0', + 'complete': 0, + 'ext': {}, + 'nodes': [{ + 'asi': 'domain.com', + 'bsid': '12345', + }, { + 'name': 'bidder', + 'domain': 'bidder.com', + 'ext': {} + }] + }, + networkName: 'myNetworkName', + networkId: 8475 + } + }; + }); + + afterEach(function () { + config.resetConfig(); + }); + + it('good strict config should append a node object to existing bid.meta.dchain object', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.nodes[1]).to.exist.and.to.deep.equal({ + 'name': 'bidder', + 'domain': 'bidder.com', + 'ext': {} + }); + expect(bid.meta.dchain.nodes[2]).to.exist.and.to.deep.equal({ asi: 'bidderA' }); + } + + config.setConfig({ dchain: { validation: STRICT } }); + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('bad strict config should delete the bid.meta.dchain object', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.not.exist; + } + + config.setConfig({ dchain: { validation: STRICT } }); + bid.meta.dchain.complete = 3; + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('relaxed config should allow bid.meta.dchain to proceed, even with bad values', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.complete).to.exist.and.to.equal(3); + expect(bid.meta.dchain.nodes[2]).to.exist.and.to.deep.equal({ asi: 'bidderA' }); + } + + config.setConfig({ dchain: { validation: RELAX } }); + bid.meta.dchain.complete = 3; + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('off config should allow the bid.meta.dchain to proceed', function () { + // check for missing nodes + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.complete).to.exist.and.to.equal(0); + expect(bid.meta.dchain.nodes).to.exist.and.to.deep.equal({ test: 123 }); + } + + config.setConfig({ dchain: { validation: OFF } }); + bid.meta.dchain.nodes = { test: 123 }; + addBidResponseHook(testCallback, adUnitCode, bid); + }); + + it('no bidder dchain', function () { + function testCallback(adUnitCode, bid) { + expect(bid.meta.dchain).to.exist; + expect(bid.meta.dchain.ver).to.exist.and.to.equal('1.0'); + expect(bid.meta.dchain.complete).to.exist.and.to.equal(0); + expect(bid.meta.dchain.nodes).to.exist.and.to.deep.equal([{ name: 'myNetworkName', bsid: '8475' }, { name: 'bidderA' }]); + } + + delete bid.meta.dchain; + config.setConfig({ dchain: { validation: RELAX } }); + addBidResponseHook(testCallback, adUnitCode, bid); + }); + }); +}); diff --git a/test/spec/modules/docereeBidAdapter_spec.js b/test/spec/modules/docereeBidAdapter_spec.js index efff2efa319..dadbb56b0c0 100644 --- a/test/spec/modules/docereeBidAdapter_spec.js +++ b/test/spec/modules/docereeBidAdapter_spec.js @@ -31,6 +31,8 @@ describe('BidlabBidAdapter', function () { bidder: 'doceree', params: { placementId: 'DOC_7jm9j5eqkl0xvc5w', + gdpr: '1', + gdprConsent: 'CPQfU1jPQfU1jG0AAAENAwCAAAAAAAAAAAAAAAAAAAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g' } }; @@ -44,6 +46,12 @@ describe('BidlabBidAdapter', function () { }); }); + describe('isGdprConsentPresent', function () { + it('Should return true if gdpr consent is present', function () { + expect(spec.isGdprConsentPresent(bid)).to.be.true; + }); + }); + describe('buildRequests', function () { let serverRequest = spec.buildRequests([bid]); serverRequest = serverRequest[0] @@ -56,7 +64,7 @@ describe('BidlabBidAdapter', function () { expect(serverRequest.method).to.equal('GET'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://bidder.doceree.com/v1/adrequest?id=DOC_7jm9j5eqkl0xvc5w&pubRequestedURL=undefined&loggedInUser=JTdCJTIyZ2VuZGVyJTIyJTNBJTIyJTIyJTJDJTIyZW1haWwlMjIlM0ElMjIlMjIlMkMlMjJoYXNoZWRFbWFpbCUyMiUzQSUyMiUyMiUyQyUyMmZpcnN0TmFtZSUyMiUzQSUyMiUyMiUyQyUyMmxhc3ROYW1lJTIyJTNBJTIyJTIyJTJDJTIybnBpJTIyJTNBJTIyJTIyJTJDJTIyaGFzaGVkTlBJJTIyJTNBJTIyJTIyJTJDJTIyY2l0eSUyMiUzQSUyMiUyMiUyQyUyMnppcENvZGUlMjIlM0ElMjIlMjIlMkMlMjJzcGVjaWFsaXphdGlvbiUyMiUzQSUyMiUyMiU3RA%3D%3D&prebidjs=true&requestId=testing&'); + expect(serverRequest.url).to.equal('https://bidder.doceree.com/v1/adrequest?id=DOC_7jm9j5eqkl0xvc5w&pubRequestedURL=undefined&loggedInUser=JTdCJTIyZ2VuZGVyJTIyJTNBJTIyJTIyJTJDJTIyZW1haWwlMjIlM0ElMjIlMjIlMkMlMjJoYXNoZWRFbWFpbCUyMiUzQSUyMiUyMiUyQyUyMmZpcnN0TmFtZSUyMiUzQSUyMiUyMiUyQyUyMmxhc3ROYW1lJTIyJTNBJTIyJTIyJTJDJTIybnBpJTIyJTNBJTIyJTIyJTJDJTIyaGFzaGVkTlBJJTIyJTNBJTIyJTIyJTJDJTIyY2l0eSUyMiUzQSUyMiUyMiUyQyUyMnppcENvZGUlMjIlM0ElMjIlMjIlMkMlMjJzcGVjaWFsaXphdGlvbiUyMiUzQSUyMiUyMiU3RA%3D%3D&prebidjs=true&requestId=testing&gdpr=1&gdpr_consent=CPQfU1jPQfU1jG0AAAENAwCAAAAAAAAAAAAAAAAAAAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g&'); }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 1277486a154..83924b38a7e 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -348,6 +348,21 @@ describe('eids array generation for known sub-modules', function() { }] }); }); + + it('qid', function() { + const userId = { + qid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'adquery.io', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); }); describe('Negative case', function() { it('eids array generation for UN-known sub-module', function() { diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index 5831a8506c1..043a8a3709e 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -432,6 +432,16 @@ describe('emx_digital Adapter', function () { }); }); + it('should add gpid to request if present', () => { + const gpid = '/12345/my-gpt-tag-0'; + let bid = utils.deepClone(bidderRequest.bids[0]); + bid.ortb2Imp = { ext: { data: { adserver: { adslot: gpid } } } }; + bid.ortb2Imp = { ext: { data: { pbadslot: gpid } } }; + let requestWithGPID = spec.buildRequests([bid], bidderRequest); + requestWithGPID = JSON.parse(requestWithGPID.data); + expect(requestWithGPID.imp[0].ext.gpid).to.exist.and.equal(gpid); + }); + it('should add UID 2.0 to request', () => { const uid2 = { id: '456' }; const bidRequestWithUID = utils.deepClone(bidderRequest); diff --git a/test/spec/modules/engageyaBidAdapter_spec.js b/test/spec/modules/engageyaBidAdapter_spec.js index ae22948994b..7f07e4b9e4a 100644 --- a/test/spec/modules/engageyaBidAdapter_spec.js +++ b/test/spec/modules/engageyaBidAdapter_spec.js @@ -4,18 +4,7 @@ import * as utils from 'src/utils.js'; const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json'; -export const _getUrlVars = function (url) { - var hash; - var myJson = {}; - var hashes = url.slice(url.indexOf('?') + 1).split('&'); - for (var i = 0; i < hashes.length; i++) { - hash = hashes[i].split('='); - myJson[hash[0]] = hash[1]; - } - return myJson; -} - -describe('engageya adapter', function () { +describe('Engageya adapter', function () { let bidRequests; let nativeBidRequests; @@ -55,40 +44,57 @@ describe('engageya adapter', function () { } ] }) + describe('isBidRequestValid', function () { - it('valid bid case', function () { + it('Valid bid case', function () { let validBid = { bidder: 'engageya', params: { widgetId: 85610, websiteId: 91140, pageUrl: '[PAGE_URL]' - } + }, + sizes: [[300, 250]] } let isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(true); + expect(isValid).to.be.true; }); - it('invalid bid case: widgetId and websiteId is not passed', function () { + it('Invalid bid case: widgetId and websiteId is not passed', function () { let validBid = { bidder: 'engageya', params: {} } let isValid = spec.isBidRequestValid(validBid); - expect(isValid).to.equal(false); + expect(isValid).to.be.false; }) - it('invalid bid case: widget id must be number', function () { + it('Invalid bid case: widget id must be number', function () { let invalidBid = { bidder: 'engageya', params: { widgetId: '157746a', websiteId: 91140, pageUrl: '[PAGE_URL]' - } + }, + sizes: [[300, 250]] + } + let isValid = spec.isBidRequestValid(invalidBid); + expect(isValid).to.be.false; + }) + + it('Invalid bid case: unsupported sizes', function () { + let invalidBid = { + bidder: 'engageya', + params: { + widgetId: '157746a', + websiteId: 91140, + pageUrl: '[PAGE_URL]' + }, + sizes: [[250, 250]] } let isValid = spec.isBidRequestValid(invalidBid); - expect(isValid).to.equal(false); + expect(isValid).to.be.false; }) }) @@ -113,36 +119,30 @@ describe('engageya adapter', function () { it('Request params check', function () { let request = spec.buildRequests(bidRequests)[0]; - const data = _getUrlVars(request.url) - expect(parseInt(data.wid)).to.exist.and.to.equal(bidRequests[0].params.widgetId); - expect(parseInt(data.webid)).to.exist.and.to.equal(bidRequests[0].params.websiteId); - }) - }) - - describe('interpretResponse', function () { - it('should return empty array if no response', function () { - const result = spec.interpretResponse({}, []) - expect(result).to.be.an('array').that.is.empty + const urlParams = new URL(request.url).searchParams; + expect(parseInt(urlParams.get('wid'))).to.exist.and.to.equal(bidRequests[0].params.widgetId); + expect(parseInt(urlParams.get('webid'))).to.exist.and.to.equal(bidRequests[0].params.websiteId); }); - it('should return empty array if no valid bids', function () { - let response = { - recs: [], - imageWidth: 300, - imageHeight: 250, - ireqId: '1d236f7890b', - pbtypeId: 2 - }; - let request = spec.buildRequests(bidRequests)[0]; - const result = spec.interpretResponse({ body: response }, request) - expect(result).to.be.an('array').that.is.empty + it('Request pageUrl - use param', function () { + const pageUrl = 'https://url.test'; + bidRequests[0].params.pageUrl = pageUrl; + const request = spec.buildRequests(bidRequests)[0]; + const urlParams = new URL(request.url).searchParams; + expect(urlParams.get('url')).to.exist.and.to.equal(pageUrl); }); + }) - it('should interpret native response', function () { - let serverResponse = { + describe('interpretResponse', function () { + let nativeResponse; + let bannerResponse; + + beforeEach(() => { + const recsResponse = { recs: [ { - ecpm: 0.0920, + ecpm: 9.20, + pecpm: 0.0520, postId: '', thumbnail_path: 'https://engageya.live/wp-content/uploads/2019/05/images.png', domain: 'domain.test', @@ -159,8 +159,80 @@ describe('engageya adapter', function () { imageWidth: 300, imageHeight: 250, ireqId: '1d236f7890b', - pbtypeId: 1 + viewPxl: '//view.pixel', }; + + nativeResponse = { + ...recsResponse, + pbtypeId: 1, + } + + bannerResponse = { + ...recsResponse, + pbtypeId: 2, + widget: { + additionalData: '{"css":".eng_tag_ttl{display:block!important}"}' + }, + } + }) + + it('should return empty array if no response', function () { + const result = spec.interpretResponse({}, []) + expect(result).to.be.an('array').that.is.empty + }); + + it('should return empty array if no valid bids', function () { + let response = { + recs: [], + imageWidth: 300, + imageHeight: 250, + ireqId: '1d236f7890b', + pbtypeId: 2, + viewPxl: '//view.pixel', + }; + let request = spec.buildRequests(bidRequests)[0]; + const result = spec.interpretResponse({ body: response }, request) + expect(result).to.be.an('array').that.is.empty + }); + + it('should interpret native response', function () { + let expectedResult = [ + { + requestId: '1d236f7890b', + cpm: 0.0520, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '', + ttl: 360, + meta: { + advertiserDomains: ['domain.test'] + }, + native: { + title: 'Test title', + body: '', + image: { + url: 'https://engageya.live/wp-content/uploads/2019/05/images.png', + width: 300, + height: 250 + }, + privacyLink: '', + clickUrl: '//click.test', + displayUrl: '//url.test', + cta: '', + sponsoredBy: 'Test displayName', + impressionTrackers: ['//impression.test', '//view.test', '//view.pixel'], + }, + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({ body: nativeResponse }, request); + expect(result).to.deep.equal(expectedResult); + }); + + it('should interpret native response - without pecpm', function () { + delete nativeResponse.recs[0].pecpm; let expectedResult = [ { requestId: '1d236f7890b', @@ -187,81 +259,76 @@ describe('engageya adapter', function () { displayUrl: '//url.test', cta: '', sponsoredBy: 'Test displayName', - impressionTrackers: ['//impression.test', '//view.test'], + impressionTrackers: ['//impression.test', '//view.test', '//view.pixel'], }, } ]; let request = spec.buildRequests(bidRequests)[0]; - let result = spec.interpretResponse({ body: serverResponse }, request); + let result = spec.interpretResponse({ body: nativeResponse }, request); expect(result).to.deep.equal(expectedResult); }); - it('should interpret display response', function () { - let serverResponse = { - recs: [ - { - ecpm: 0.0920, - postId: '', - thumbnail_path: 'https://engageya.live/wp-content/uploads/2019/05/images.png', - domain: 'domain.test', + it('should interpret native response - without trackers', function () { + delete nativeResponse.recs[0].trackers; + let expectedResult = [ + { + requestId: '1d236f7890b', + cpm: 0.0520, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '', + ttl: 360, + meta: { + advertiserDomains: ['domain.test'] + }, + native: { title: 'Test title', + body: '', + image: { + url: 'https://engageya.live/wp-content/uploads/2019/05/images.png', + width: 300, + height: 250 + }, + privacyLink: '', clickUrl: '//click.test', - url: '//url.test', - displayName: 'Test displayName', - trackers: { - impressionPixels: ['//impression.test'], - viewPixels: ['//view.test'], - } - } - ], - imageWidth: 300, - imageHeight: 250, - ireqId: '1d236f7890b', - pbtypeId: 2, - widget: { - additionalData: '{"css":".eng_tag_ttl{display:block!important}"}' + displayUrl: '//url.test', + cta: '', + sponsoredBy: 'Test displayName', + impressionTrackers: ['//view.pixel'], + }, } - }; + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({ body: nativeResponse }, request); + expect(result).to.deep.equal(expectedResult); + }); + + it('should interpret display response', function () { let expectedResult = [ { requestId: '1d236f7890b', - cpm: 0.0920, + cpm: 0.0520, width: 300, height: 250, - netRevenue: false, + netRevenue: true, currency: 'USD', creativeId: '', ttl: 360, meta: { advertiserDomains: ['domain.test'] }, - ad: ``, + ad: ``, } ]; let request = spec.buildRequests(bidRequests)[0]; - let result = spec.interpretResponse({ body: serverResponse }, request); + let result = spec.interpretResponse({ body: bannerResponse }, request); expect(result).to.deep.equal(expectedResult); }); - it('should interpret display response without title', function () { - let serverResponse = { - recs: [ - { - ecpm: 0.0920, - postId: '', - thumbnail_path: 'https://engageya.live/wp-content/uploads/2019/05/images.png', - domain: 'domain.test', - title: ' ', - clickUrl: '//click.test', - url: '//url.test', - displayName: 'Test displayName', - } - ], - imageWidth: 300, - imageHeight: 250, - ireqId: '1d236f7890b', - pbtypeId: 2, - }; + it('should interpret display response - without pecpm', function () { + delete bannerResponse.recs[0].pecpm; let expectedResult = [ { requestId: '1d236f7890b', @@ -275,11 +342,80 @@ describe('engageya adapter', function () { meta: { advertiserDomains: ['domain.test'] }, - ad: `
`, + ad: ``, + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({ body: bannerResponse }, request); + expect(result).to.deep.equal(expectedResult); + }); + + it('should interpret display response - without title', function () { + bannerResponse.recs[0].title = ' '; + let expectedResult = [ + { + requestId: '1d236f7890b', + cpm: 0.0520, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '', + ttl: 360, + meta: { + advertiserDomains: ['domain.test'] + }, + ad: `
`, + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({ body: bannerResponse }, request); + expect(result).to.deep.equal(expectedResult); + }); + + it('should interpret display response - without widget additional data', function () { + bannerResponse.widget.additionalData = null; + let expectedResult = [ + { + requestId: '1d236f7890b', + cpm: 0.0520, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '', + ttl: 360, + meta: { + advertiserDomains: ['domain.test'] + }, + ad: ``, + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({ body: bannerResponse }, request); + expect(result).to.deep.equal(expectedResult); + }); + + it('should interpret display response - without trackers', function () { + bannerResponse.recs[0].trackers = null; + let expectedResult = [ + { + requestId: '1d236f7890b', + cpm: 0.0520, + width: 300, + height: 250, + netRevenue: true, + currency: 'USD', + creativeId: '', + ttl: 360, + meta: { + advertiserDomains: ['domain.test'] + }, + ad: ``, } ]; let request = spec.buildRequests(bidRequests)[0]; - let result = spec.interpretResponse({ body: serverResponse }, request); + let result = spec.interpretResponse({ body: bannerResponse }, request); expect(result).to.deep.equal(expectedResult); }); }) diff --git a/test/spec/modules/feedadBidAdapter_spec.js b/test/spec/modules/feedadBidAdapter_spec.js index 6b75af0d55d..2739654eb5d 100644 --- a/test/spec/modules/feedadBidAdapter_spec.js +++ b/test/spec/modules/feedadBidAdapter_spec.js @@ -171,6 +171,21 @@ describe('FeedAdAdapter', function () { expect(result.data.bids).to.be.lengthOf(1); expect(result.data.bids[0]).to.deep.equal(bid); }); + it('should pass through additional bid parameters', function () { + let bid = { + code: 'feedad', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: {clientToken: 'clientToken', placementId: 'placement-id', another: 'parameter', more: 'parameters'} + }; + let result = spec.buildRequests([bid], bidderRequest); + expect(result.data.bids).to.be.lengthOf(1); + expect(result.data.bids[0].params.another).to.equal('parameter'); + expect(result.data.bids[0].params.more).to.equal('parameters'); + }); it('should detect empty media types', function () { let bid = { code: 'feedad', diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index a5b4bd2a03f..81f4ecec074 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -98,6 +98,19 @@ describe('freewheelSSP BidAdapter Test', () => { 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'example.com', + 'sid': '0', + 'hp': 1, + 'rid': 'bidrequestid', + 'domain': 'example.com' + } + ] + } } ]; @@ -112,6 +125,12 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.playerSize).to.equal('300x600'); }); + it('should return a properly formatted request with schain defined', function () { + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload.schain).to.deep.equal(bidRequests[0].schain) + }); + it('sends bid request to ENDPOINT via GET', () => { const request = spec.buildRequests(bidRequests); expect(request[0].url).to.contain(ENDPOINT); diff --git a/test/spec/modules/glimpseBidAdapter_spec.js b/test/spec/modules/glimpseBidAdapter_spec.js index 98e1a1bb451..02f5b502a1b 100644 --- a/test/spec/modules/glimpseBidAdapter_spec.js +++ b/test/spec/modules/glimpseBidAdapter_spec.js @@ -1,3 +1,4 @@ +import { BANNER } from '../../../src/mediaTypes' import { expect } from 'chai' import { newBidder } from 'src/adapters/bidderFactory.js' import { spec } from 'modules/glimpseBidAdapter.js' @@ -79,6 +80,20 @@ function getDeepCopy(object) { describe('GlimpseProtocolAdapter', () => { const glimpseAdapter = newBidder(spec) + describe('spec', () => { + it('Has defined the glimpse gvlid', () => { + expect(spec.gvlid).to.equal(1012) + }) + + it('Has defined glimpse as the bidder', () => { + expect(spec.code).to.equal('glimpse') + }) + + it('Has defined valid mediaTypes', () => { + expect(spec.supportedMediaTypes).to.deep.equal([BANNER]) + }) + }) + describe('Inherited functions', () => { it('Functions exist and are valid types', () => { expect(glimpseAdapter.callBids).to.exist.and.to.be.a('function') diff --git a/test/spec/modules/gnetBidAdapter_spec.js b/test/spec/modules/gnetBidAdapter_spec.js index eeb33418a82..f833aa87042 100644 --- a/test/spec/modules/gnetBidAdapter_spec.js +++ b/test/spec/modules/gnetBidAdapter_spec.js @@ -8,7 +8,7 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; -const ENDPOINT = 'https://adserver.gnetproject.com/prebid.php'; +const ENDPOINT = 'https://service.gnetrtb.com/api/adrequest'; describe('gnetAdapter', function () { const adapter = newBidder(spec); @@ -23,7 +23,7 @@ describe('gnetAdapter', function () { let bid = { bidder: 'gnet', params: { - websiteId: '4' + websiteId: '1', adunitId: '1' } }; @@ -43,7 +43,7 @@ describe('gnetAdapter', function () { const bidRequests = [{ bidder: 'gnet', params: { - websiteId: '4' + websiteId: '1', adunitId: '1' }, adUnitCode: '/150790500/4_ZONA_IAB_300x250_5', sizes: [ @@ -57,7 +57,7 @@ describe('gnetAdapter', function () { const bidderRequest = { refererInfo: { - referer: 'https://gnetproject.com/' + referer: 'https://gnetrtb.com' } }; @@ -66,13 +66,13 @@ describe('gnetAdapter', function () { expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('POST'); expect(requests[0].data).to.equal(JSON.stringify({ - 'referer': 'https://gnetproject.com/', + 'referer': 'https://gnetrtb.com', 'adUnitCode': '/150790500/4_ZONA_IAB_300x250_5', 'bidId': '2a19afd5173318', 'transactionId': '894bdff6-61ec-4bec-a5a9-f36a5bfccef5', 'sizes': ['300x250'], 'params': { - 'websiteId': '4' + 'websiteId': '1', 'adunitId': '1' } })); }); diff --git a/test/spec/modules/goldbachBidAdapter_spec.js b/test/spec/modules/goldbachBidAdapter_spec.js new file mode 100644 index 00000000000..459cda7958f --- /dev/null +++ b/test/spec/modules/goldbachBidAdapter_spec.js @@ -0,0 +1,1359 @@ +import { expect } from 'chai'; +import { spec } from 'modules/goldbachBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import * as bidderFactory from 'src/adapters/bidderFactory.js'; +import { auctionManager } from 'src/auctionManager.js'; +import { deepClone } from 'src/utils.js'; +import { config } from 'src/config.js'; + +const ENDPOINT = 'https://ib.adnxs.com/ut/v3/prebid'; +const PRICING_ENDPOINT = 'https://templates.da-services.ch/01_universal/burda_prebid/1.0/json/sizeCPMMapping.json'; + +describe('GoldbachXandrAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'goldbach', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return true when required params found', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'member': '1234', + 'invCode': 'ABCD' + }; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'placementId': 0 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let getAdUnitsStub; + let bidRequests = [ + { + 'bidder': 'goldbach', + 'params': { + 'placementId': '10433394' + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' + } + ]; + + beforeEach(function() { + getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { + return []; + }); + }); + + afterEach(function() { + getAdUnitsStub.restore(); + }); + + it('should parse out private sizes', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + privateSizes: [300, 250] + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].private_sizes).to.exist; + expect(payload.tags[0].private_sizes).to.deep.equal([{width: 300, height: 250}]); + }); + + it('should add publisher_id in request', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + publisherId: '1231234' + } + }); + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].publisher_id).to.exist; + expect(payload.tags[0].publisher_id).to.deep.equal(1231234); + expect(payload.publisher_id).to.exist; + expect(payload.publisher_id).to.deep.equal(1231234); + }) + + it('should add source and verison to the tag', function () { + const request = spec.buildRequests(bidRequests)[1]; + const payload = JSON.parse(request.data); + expect(payload.sdk).to.exist; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + }); + + it('should populate the ad_types array on all requests', function () { + let adUnits = [{ + code: 'adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'goldbach', + params: { + placementId: '10433394' + } + }], + transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' + }]; + + ['banner', 'video', 'native'].forEach(type => { + getAdUnitsStub.callsFake(function(...args) { + return adUnits; + }); + + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes[type] = {}; + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal([type]); + + if (type === 'banner') { + delete adUnits[0].mediaTypes; + } + }); + }); + + it('should not populate the ad_types array when adUnit.mediaTypes is undefined', function() { + const bidRequest = Object.assign({}, bidRequests[0]); + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.not.exist; + }); + + it('should populate the ad_types array on outstream requests', function () { + const bidRequest = Object.assign({}, bidRequests[0]); + bidRequest.mediaTypes = {}; + bidRequest.mediaTypes.video = {context: 'outstream'}; + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].ad_types).to.deep.equal(['video']); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests)[1]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('sends bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(PRICING_ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('should attach valid video params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + video: { + id: 123, + minduration: 100, + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + id: 123, + minduration: 100 + }); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('should include ORTB video values when video params were not set', function() { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.params = { + placementId: '1234235', + video: { + skippable: true, + playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], + context: 'outstream' + } + }; + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + + it('should add video property when adUnit includes a renderer', function () { + const videoData = { + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mp4'] + } + }, + params: { + placementId: '10433394', + video: { + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + }; + + let bidRequest1 = deepClone(bidRequests[0]); + bidRequest1 = Object.assign({}, bidRequest1, videoData, { + renderer: { + url: 'https://test.renderer.url', + render: function () {} + } + }); + + let bidRequest2 = deepClone(bidRequests[0]); + bidRequest2.adUnitCode = 'adUnit_code_2'; + bidRequest2 = Object.assign({}, bidRequest2, videoData); + + const request = spec.buildRequests([bidRequest1, bidRequest2])[1]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].video).to.deep.equal({ + skippable: true, + playback_method: 2, + custom_renderer_present: true + }); + expect(payload.tags[1].video).to.deep.equal({ + skippable: true, + playback_method: 2 + }); + }); + + it('should attach valid user params to the tag', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + user: { + externalUid: '123', + segments: [123, { id: 987, value: 876 }], + foobar: 'invalid' + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.user).to.exist; + expect(payload.user).to.deep.equal({ + external_uid: '123', + segments: [{id: 123}, {id: 987, value: 876}] + }); + }); + + it('should attach reserve param when either bid param or getFloor function exists', function () { + let getFloorResponse = { currency: 'USD', floor: 3 }; + let request, payload = null; + let bidRequest = deepClone(bidRequests[0]); + + // 1 -> reserve not defined, getFloor not defined > empty + request = spec.buildRequests([bidRequest])[1]; + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.not.exist; + + // 2 -> reserve is defined, getFloor not defined > reserve is used + bidRequest.params = { + 'placementId': '10433394', + 'reserve': 0.5 + }; + request = spec.buildRequests([bidRequest])[1]; + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.exist.and.to.equal(0.5); + + // 3 -> reserve is defined, getFloor is defined > getFloor is used + bidRequest.getFloor = () => getFloorResponse; + + request = spec.buildRequests([bidRequest])[1]; + payload = JSON.parse(request.data); + + expect(payload.tags[0].reserve).to.exist.and.to.equal(3); + }); + + it('should duplicate adpod placements into batches and set correct maxduration', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + + // 300 / 15 = 20 total + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload1.tags[0].video.maxduration).to.equal(30); + + expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); + expect(payload2.tags[0].video.maxduration).to.equal(30); + }); + + it('should round down adpod placements when numbers are uneven', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 123, + durationRangeSec: [45], + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(2); + }); + + it('should duplicate adpod placements when requireExactDuration is set', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + requireExactDuration: true, + } + } + } + ); + + // 20 total placements with 15 max impressions = 2 requests + const request = spec.buildRequests([bidRequest])[1]; + expect(request.length).to.equal(2); + + // 20 spread over 2 requests = 15 in first request, 5 in second + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(5); + + // 10 placements should have max/min at 15 + // 10 placemenst should have max/min at 30 + const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); + const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); + expect(payload1tagsWith15.length).to.equal(10); + expect(payload1tagsWith30.length).to.equal(5); + + // 5 placemenst with min/max at 30 were in the first request + // so 5 remaining should be in the second + const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); + expect(payload2tagsWith30.length).to.equal(5); + }); + + it('should set durations for placements when requireExactDuration is set and numbers are uneven', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 105, + durationRangeSec: [15, 30, 60], + requireExactDuration: true, + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.tags.length).to.equal(7); + + const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); + const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); + const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); + expect(tagsWith15.length).to.equal(3); + expect(tagsWith30.length).to.equal(3); + expect(tagsWith60.length).to.equal(1); + }); + + it('should break adpod request into batches', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { placementId: '14542875' } + }, + { + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 225, + durationRangeSec: [5], + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload1 = JSON.parse(request[0].data); + const payload2 = JSON.parse(request[1].data); + const payload3 = JSON.parse(request[2].data); + + expect(payload1.tags.length).to.equal(15); + expect(payload2.tags.length).to.equal(15); + expect(payload3.tags.length).to.equal(15); + }); + + // it('should contain hb_source value for adpod', function() { + // let bidRequest = Object.assign({}, + // bidRequests[0], + // { + // params: { placementId: '14542875' } + // }, + // { + // mediaTypes: { + // video: { + // context: 'adpod', + // playerSize: [640, 480], + // adPodDurationSec: 300, + // durationRangeSec: [15, 30], + // } + // } + // } + // ); + // const request = spec.buildRequests([bidRequest])[1]; + // const payload = JSON.parse(request.data); + // expect(payload.tags[0].hb_source).to.deep.equal(7); + // }); + + it('should contain hb_source value for other media', function() { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'banner', + params: { + sizes: [[300, 250], [300, 600]], + placementId: 13144370 + } + } + ); + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.tags[0].hb_source).to.deep.equal(1); + }); + + it('adds brand_category_exclusion to request when set', function() { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon + .stub(config, 'getConfig') + .withArgs('adpod.brandCategoryExclusion') + .returns(true); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.brand_category_uniqueness).to.equal(true); + + config.getConfig.restore(); + }); + + it('should attach native params to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + title: {required: true}, + body: {required: true}, + body2: {required: true}, + image: {required: true, sizes: [100, 100]}, + icon: {required: true}, + cta: {required: false}, + rating: {required: true}, + sponsoredBy: {required: true}, + privacyLink: {required: true}, + displayUrl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + salePrice: {required: true} + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].native.layouts[0]).to.deep.equal({ + title: {required: true}, + description: {required: true}, + desc2: {required: true}, + main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, + icon: {required: true}, + ctatext: {required: false}, + rating: {required: true}, + sponsored_by: {required: true}, + privacy_link: {required: true}, + displayurl: {required: true}, + address: {required: true}, + downloads: {required: true}, + likes: {required: true}, + phone: {required: true}, + price: {required: true}, + saleprice: {required: true}, + privacy_supported: true + }); + expect(payload.tags[0].hb_source).to.equal(1); + }); + + it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + mediaType: 'native', + nativeParams: { + image: { required: true } + } + } + ); + bidRequest.sizes = [[150, 100], [300, 250]]; + + let request = spec.buildRequests([bidRequest])[1]; + let payload = JSON.parse(request.data); + expect(payload.tags[0].sizes).to.deep.equal([{width: 150, height: 100}, {width: 300, height: 250}]); + + delete bidRequest.sizes; + + request = spec.buildRequests([bidRequest])[1]; + payload = JSON.parse(request.data); + + expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); + }); + + it('should convert keyword params to proper form and attaches to request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + keywords: { + single: 'val', + singleArr: ['val'], + singleArrNum: [5], + multiValMixed: ['value1', 2, 'value3'], + singleValNum: 123, + emptyStr: '', + emptyArr: [''], + badValue: {'foo': 'bar'} // should be dropped + } + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].keywords).to.deep.equal([{ + 'key': 'single', + 'value': ['val'] + }, { + 'key': 'singleArr', + 'value': ['val'] + }, { + 'key': 'singleArrNum', + 'value': ['5'] + }, { + 'key': 'multiValMixed', + 'value': ['value1', '2', 'value3'] + }, { + 'key': 'singleValNum', + 'value': ['123'] + }, { + 'key': 'emptyStr' + }, { + 'key': 'emptyArr' + }]); + }); + + it('should add payment rules to the request', function () { + let bidRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + usePaymentRule: true + } + } + ); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].use_pmt_rule).to.equal(true); + }); + + it('should add gpid to the request', function () { + let testGpid = '/12345/my-gpt-tag-0'; + let bidRequest = deepClone(bidRequests[0]); + bidRequest.ortb2Imp = { ext: { data: { pbadslot: testGpid } } }; + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.tags[0].gpid).to.exist.and.equal(testGpid) + }); + + it('should add gdpr consent information to the request', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'goldbach', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true, + addtlConsent: '1~7.12.35.62.66.70.89.93.108' + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest)[1]; + expect(request.options).to.deep.equal({withCredentials: true}); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_consent).to.exist; + expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); + expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; + expect(payload.gdpr_consent.addtl_consent).to.exist.and.to.deep.equal([7, 12, 35, 62, 66, 70, 89, 93, 108]); + }); + + it('should add us privacy string to payload', function() { + let consentString = '1YA-'; + let bidderRequest = { + 'bidderCode': 'goldbach', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': consentString + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest)[1]; + const payload = JSON.parse(request.data); + + expect(payload.us_privacy).to.exist; + expect(payload.us_privacy).to.exist.and.to.equal(consentString); + }); + + it('supports sending hybrid mobile app parameters', function () { + let appRequest = Object.assign({}, + bidRequests[0], + { + params: { + placementId: '10433394', + app: { + id: 'B1O2W3M4AN.com.prebid.webview', + geo: { + lat: 40.0964439, + lng: -75.3009142 + }, + device_id: { + idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', // Apple advertising identifier + aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', // Android advertising identifier + md5udid: '5756ae9022b2ea1e47d84fead75220c8', // MD5 hash of the ANDROID_ID + sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', // SHA1 hash of the ANDROID_ID + windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' // Windows advertising identifier + } + } + } + } + ); + const request = spec.buildRequests([appRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.app).to.exist; + expect(payload.app).to.deep.equal({ + appid: 'B1O2W3M4AN.com.prebid.webview' + }); + expect(payload.device.device_id).to.exist; + expect(payload.device.device_id).to.deep.equal({ + aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', + idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', + md5udid: '5756ae9022b2ea1e47d84fead75220c8', + sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', + windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' + }); + expect(payload.device.geo).to.exist; + expect(payload.device.geo).to.deep.equal({ + lat: 40.0964439, + lng: -75.3009142 + }); + }); + + it('should add referer info to payload', function () { + const bidRequest = Object.assign({}, bidRequests[0]) + const bidderRequest = { + refererInfo: { + referer: 'https://example.com/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'https://example.com/page.html', + 'https://example.com/iframe1.html', + 'https://example.com/iframe2.html' + ] + } + } + const request = spec.buildRequests([bidRequest], bidderRequest)[1]; + const payload = JSON.parse(request.data); + + expect(payload.referrer_detection).to.exist; + expect(payload.referrer_detection).to.deep.equal({ + rd_ref: 'https%3A%2F%2Fexample.com%2Fpage.html', + rd_top: true, + rd_ifs: 2, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + }); + }); + + it('should populate schain if available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'blob.com', + 'sid': '001', + 'hp': 1 + } + ] + } + }); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'blob.com', + 'sid': '001', + 'hp': 1 + } + ] + }); + }); + + it('should populate coppa if set in config', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + + expect(payload.user.coppa).to.equal(true); + + config.getConfig.restore(); + }); + + it('should set the X-Is-Test customHeader if test flag is enabled', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + sinon.stub(config, 'getConfig') + .withArgs('apn_test') + .returns(true); + + const request = spec.buildRequests([bidRequest])[1]; + expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); + + config.getConfig.restore(); + }); + + it('should always set withCredentials: true on the request.options', function () { + let bidRequest = Object.assign({}, bidRequests[0]); + const request = spec.buildRequests([bidRequest])[1]; + expect(request.options.withCredentials).to.equal(true); + }); + + it('should set simple domain variant if purpose 1 consent is not given', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let bidderRequest = { + 'bidderCode': 'goldbach', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + consentString: consentString, + gdprApplies: true, + apiVersion: 2, + vendorData: { + purpose: { + consents: { + 1: false + } + } + } + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest)[1]; + expect(request.url).to.equal('https://ib.adnxs-simple.com/ut/v3/prebid'); + }); + + it('should populate eids when supported userIds are available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + userId: { + tdid: 'sample-userid', + uid2: { id: 'sample-uid2-value' }, + criteoId: 'sample-criteo-userid', + netId: 'sample-netId-userid', + idl_env: 'sample-idl-userid', + flocId: { + id: 'sample-flocid-value', + version: 'chrome.1.0' + } + } + }); + + const request = spec.buildRequests([bidRequest])[1]; + const payload = JSON.parse(request.data); + expect(payload.eids).to.deep.include({ + source: 'adserver.org', + id: 'sample-userid', + rti_partner: 'TDID' + }); + + expect(payload.eids).to.deep.include({ + source: 'criteo.com', + id: 'sample-criteo-userid', + }); + + expect(payload.eids).to.deep.include({ + source: 'chrome.com', + id: 'sample-flocid-value' + }); + + expect(payload.eids).to.deep.include({ + source: 'netid.de', + id: 'sample-netId-userid', + }); + + expect(payload.eids).to.deep.include({ + source: 'liveramp.com', + id: 'sample-idl-userid' + }); + + expect(payload.eids).to.deep.include({ + source: 'uidapi.com', + id: 'sample-uid2-value', + rti_partner: 'UID2' + }); + }); + + it('should populate iab_support object at the root level if omid support is detected', function () { + // with bid.params.frameworks + let bidRequest_A = Object.assign({}, bidRequests[0], { + params: { + frameworks: [1, 2, 5, 6], + video: { + frameworks: [1, 2, 5, 6] + } + } + }); + let request = spec.buildRequests([bidRequest_A])[1]; + let payload = JSON.parse(request.data); + expect(payload.iab_support).to.be.an('object'); + expect(payload.iab_support).to.deep.equal({ + omidpn: 'Appnexus', + omidpv: '$prebid.version$' + }); + expect(payload.tags[0].banner_frameworks).to.be.an('array'); + expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video_frameworks).to.be.an('array'); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); + expect(payload.tags[0].video.frameworks).to.not.exist; + + // without bid.params.frameworks + const bidRequest_B = Object.assign({}, bidRequests[0]); + request = spec.buildRequests([bidRequest_B])[1]; + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + + // with video.frameworks but it is not an array + const bidRequest_C = Object.assign({}, bidRequests[0], { + params: { + video: { + frameworks: "'1', '2', '3', '6'" + } + } + }); + request = spec.buildRequests([bidRequest_C])[1]; + payload = JSON.parse(request.data); + expect(payload.iab_support).to.not.exist; + expect(payload.tags[0].banner_frameworks).to.not.exist; + expect(payload.tags[0].video_frameworks).to.not.exist; + }); + }) + + describe('interpretResponse', function () { + let bfStub; + before(function() { + bfStub = sinon.stub(bidderFactory, 'getIabSubCategory'); + }); + + after(function() { + bfStub.restore(); + }); + + let response = { + 'version': '3.0.0', + 'tags': [ + { + 'uuid': '3db3773286ee59', + 'tag_id': 10433394, + 'auction_id': '4534722592064951574', + 'nobid': false, + 'no_ad_url': 'https://lax1-ib.adnxs.com/no-ad', + 'timeout_ms': 10000, + 'ad_profile_id': 27079, + 'ads': [ + { + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 958, + 'creative_id': 29681110, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 0.5, + 'cpm_publisher_currency': 0.5, + 'publisher_currency_code': '$', + 'client_initiated_ad_counting': true, + 'viewability': { + 'config': '' + }, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'https://lax1-ib.adnxs.com/impression' + ], + 'video_events': {} + } + ] + } + } + ] + } + ] + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '3db3773286ee59', + 'cpm': 0.5, + 'creativeId': 29681110, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true, + 'adUnitCode': 'code', + 'appnexus': { + 'buyerMemberId': 958 + } + } + ]; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function () { + let response = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result.length).to.equal(0); + }); + + it('handles outstream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastXml'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles instream video responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/vid' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'instream' + } + } + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('handles adpod responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'brand_category_id': 10, + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'asset_url': 'https://sample.vastURL.com/here/adpod', + 'duration_ms': 30000, + } + }, + 'viewability': { + 'config': '' + } + }] + }] + }; + + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } + } + }] + }; + bfStub.returns('1'); + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0].video.context).to.equal('adpod'); + expect(result[0].video.durationSeconds).to.equal(30); + }); + + it('handles native responses', function () { + let response1 = deepClone(response); + response1.tags[0].ads[0].ad_type = 'native'; + response1.tags[0].ads[0].rtb.native = { + 'title': 'Native Creative', + 'desc': 'Cool description great stuff', + 'desc2': 'Additional body text', + 'ctatext': 'Do it', + 'sponsored': 'AppNexus', + 'icon': { + 'width': 0, + 'height': 0, + 'url': 'https://cdn.adnxs.com/icon.png' + }, + 'main_img': { + 'width': 2352, + 'height': 1516, + 'url': 'https://cdn.adnxs.com/img.png' + }, + 'link': { + 'url': 'https://www.appnexus.com', + 'fallback_url': '', + 'click_trackers': ['https://nym1-ib.adnxs.com/click'] + }, + 'impression_trackers': ['https://example.com'], + 'rating': '5', + 'displayurl': 'https://AppNexus.com/?url=display_url', + 'likes': '38908320', + 'downloads': '874983', + 'price': '9.99', + 'saleprice': 'FREE', + 'phone': '1234567890', + 'address': '28 W 23rd St, New York, NY 10010', + 'privacy_link': 'https://appnexus.com/?url=privacy_url', + 'javascriptTrackers': '' + }; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + + let result = spec.interpretResponse({ body: response1 }, {bidderRequest}); + expect(result[0].native.title).to.equal('Native Creative'); + expect(result[0].native.body).to.equal('Cool description great stuff'); + expect(result[0].native.cta).to.equal('Do it'); + expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); + }); + + it('supports configuring outstream renderers', function () { + const outstreamResponse = deepClone(response); + outstreamResponse.tags[0].ads[0].rtb.video = {}; + outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; + + const bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + renderer: { + options: { + adText: 'configured' + } + }, + mediaTypes: { + video: { + context: 'outstream' + } + } + }] + }; + + const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); + expect(result[0].renderer.config).to.deep.equal( + bidderRequest.bids[0].renderer.options + ); + }); + + it('should add deal_priority and deal_code', function() { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].ad_type = 'video'; + responseWithDeal.tags[0].ads[0].deal_priority = 5; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + responseWithDeal.tags[0].ads[0].rtb.video = { + duration_ms: 1500, + player_width: 640, + player_height: 340, + }; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code', + mediaTypes: { + video: { + context: 'adpod' + } + } + }] + } + let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); + expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + expect(result[0].video.dealTier).to.equal(5); + }); + + it('should add advertiser id', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); + }); + + it('should add advertiserDomains', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].adomain = ['123']; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['advertiserDomains']); + expect(Object.keys(result[0].meta.advertiserDomains)).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index 3e8dbfe8d92..2bfce71806e 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -20,7 +20,9 @@ describe('GPT pre-auction module', () => { const testSlots = [ makeSlot({ code: 'slotCode1', divId: 'div1' }), makeSlot({ code: 'slotCode2', divId: 'div2' }), - makeSlot({ code: 'slotCode3', divId: 'div3' }) + makeSlot({ code: 'slotCode3', divId: 'div3' }), + makeSlot({ code: 'slotCode4', divId: 'div4' }), + makeSlot({ code: 'slotCode4', divId: 'div5' }) ]; describe('appendPbAdSlot', () => { @@ -172,7 +174,9 @@ describe('GPT pre-auction module', () => { expect(_currentConfig).to.deep.equal({ enabled: true, customGptSlotMatching: false, - customPbAdSlot: false + customPbAdSlot: false, + customPreAuction: false, + useDefaultPreAuction: false }); }); }); @@ -197,15 +201,240 @@ describe('GPT pre-auction module', () => { code: 'slotCode3', }]; + // first two adUnits directly pass in pbadslot => gpid is same const expectedAdUnits = [{ code: 'adUnit1', - ortb2Imp: { ext: { data: { pbadslot: '12345' } } } - }, { + ortb2Imp: { + ext: { + data: { + pbadslot: '12345' + }, + gpid: '12345' + } + } + }, + // second adunit + { code: 'slotCode1', - ortb2Imp: { ext: { data: { pbadslot: '67890', adserver: { name: 'gam', adslot: 'slotCode1' } } } } + ortb2Imp: { + ext: { + data: { + pbadslot: '67890', + adserver: { + name: 'gam', + adslot: 'slotCode1' + } + }, + gpid: '67890' + } + } }, { code: 'slotCode3', - ortb2Imp: { ext: { data: { pbadslot: 'slotCode3', adserver: { name: 'gam', adslot: 'slotCode3' } } } } + ortb2Imp: { + ext: { + data: { + pbadslot: 'slotCode3', + adserver: { + name: 'gam', + adslot: 'slotCode3' + } + }, + gpid: 'slotCode3' + } + } + }]; + + window.googletag.pubads().setSlots(testSlots); + runMakeBidRequests(testAdUnits); + expect(returnedAdUnits).to.deep.equal(expectedAdUnits); + }); + + it('should not apply gpid if pbadslot was set by adUnitCode', () => { + const testAdUnits = [{ + code: 'noMatchCode', + }]; + + // first two adUnits directly pass in pbadslot => gpid is same + const expectedAdUnits = [{ + code: 'noMatchCode', + ortb2Imp: { + ext: { + data: { + pbadslot: 'noMatchCode' + }, + } + } + }]; + + window.googletag.pubads().setSlots(testSlots); + runMakeBidRequests(testAdUnits); + expect(returnedAdUnits).to.deep.equal(expectedAdUnits); + }); + + it('should use the passed customPreAuction logic', () => { + let counter = 0; + config.setConfig({ + gptPreAuction: { + enabled: true, + customPreAuction: (adUnit, slotName) => { + counter += 1; + return `${adUnit.code}-${slotName || counter}`; + } + } + }); + const testAdUnits = [ + { + code: 'adUnit1', + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } + }, + { + code: 'adUnit2', + }, + { + code: 'slotCode3', + }, + { + code: 'div4', + } + ]; + + // all slots should be passed in same time and have slot-${index} + const expectedAdUnits = [{ + code: 'adUnit1', + ortb2Imp: { + ext: { + // no slotname match so uses adUnit.code-counter + data: { + pbadslot: 'adUnit1-1' + }, + gpid: 'adUnit1-1' + } + } + }, + // second adunit + { + code: 'adUnit2', + ortb2Imp: { + ext: { + // no slotname match so uses adUnit.code-counter + data: { + pbadslot: 'adUnit2-2' + }, + gpid: 'adUnit2-2' + } + } + }, { + code: 'slotCode3', + ortb2Imp: { + ext: { + // slotname found, so uses code + slotname (which is same) + data: { + pbadslot: 'slotCode3-slotCode3', + adserver: { + name: 'gam', + adslot: 'slotCode3' + } + }, + gpid: 'slotCode3-slotCode3' + } + } + }, { + code: 'div4', + ortb2Imp: { + ext: { + // slotname found, so uses code + slotname + data: { + pbadslot: 'div4-slotCode4', + adserver: { + name: 'gam', + adslot: 'slotCode4' + } + }, + gpid: 'div4-slotCode4' + } + } + }]; + + window.googletag.pubads().setSlots(testSlots); + runMakeBidRequests(testAdUnits); + expect(returnedAdUnits).to.deep.equal(expectedAdUnits); + }); + + it('should use useDefaultPreAuction logic', () => { + config.setConfig({ + gptPreAuction: { + enabled: true, + useDefaultPreAuction: true + } + }); + const testAdUnits = [ + // First adUnit should use the preset pbadslot + { + code: 'adUnit1', + ortb2Imp: { ext: { data: { pbadslot: '12345' } } } + }, + // Second adUnit should not match a gam slot, so no slot set + { + code: 'adUnit2', + }, + // third adunit matches a single slot so uses it + { + code: 'slotCode3', + }, + // fourth adunit matches multiple slots so combination + { + code: 'div4', + } + ]; + + const expectedAdUnits = [{ + code: 'adUnit1', + ortb2Imp: { + ext: { + data: { + pbadslot: '12345' + }, + gpid: '12345' + } + } + }, + // second adunit + { + code: 'adUnit2', + ortb2Imp: { + ext: { + data: { + }, + } + } + }, { + code: 'slotCode3', + ortb2Imp: { + ext: { + data: { + pbadslot: 'slotCode3', + adserver: { + name: 'gam', + adslot: 'slotCode3' + } + }, + gpid: 'slotCode3' + } + } + }, { + code: 'div4', + ortb2Imp: { + ext: { + data: { + pbadslot: 'slotCode4#div4', + adserver: { + name: 'gam', + adslot: 'slotCode4' + } + }, + gpid: 'slotCode4#div4' + } + } }]; window.googletag.pubads().setSlots(testSlots); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index dfd7db7d922..93b863bf116 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -152,6 +152,12 @@ describe('gumgumAdapter', function () { const zoneParam = { 'zone': '123a' }; const pubIdParam = { 'pubId': 123 }; + it('should set aun if the adUnitCode is available', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.aun).to.equal(bidRequests[0].adUnitCode); + }); + it('should set pubId param if found', function () { const request = { ...bidRequests[0], params: pubIdParam }; const bidRequest = spec.buildRequests([request])[0]; diff --git a/test/spec/modules/idImportLibrary_spec.js b/test/spec/modules/idImportLibrary_spec.js index 699c2c43a94..9f7c0363571 100644 --- a/test/spec/modules/idImportLibrary_spec.js +++ b/test/spec/modules/idImportLibrary_spec.js @@ -3,15 +3,20 @@ import * as idImportlibrary from 'modules/idImportLibrary.js'; var expect = require('chai').expect; -describe('currency', function () { - let fakeCurrencyFileServer; +const mockMutationObserver = { + observe: () => { + return null + } +} + +describe('IdImportLibrary Tests', function () { + let fakeServer; let sandbox; let clock; - let fn = sinon.spy(); beforeEach(function () { - fakeCurrencyFileServer = sinon.fakeServer.create(); + fakeServer = sinon.fakeServer.create(); sinon.stub(utils, 'logInfo'); sinon.stub(utils, 'logError'); }); @@ -19,7 +24,7 @@ describe('currency', function () { afterEach(function () { utils.logInfo.restore(); utils.logError.restore(); - fakeCurrencyFileServer.restore(); + fakeServer.restore(); idImportlibrary.setConfig({}); }); @@ -34,28 +39,210 @@ describe('currency', function () { clock.restore(); }); - it('results when no config available', function () { + it('results when no config is set', function () { + idImportlibrary.setConfig(); + sinon.assert.called(utils.logError); + }); + it('results when config is empty', function () { idImportlibrary.setConfig({}); sinon.assert.called(utils.logError); }); - it('results with config available', function () { - idImportlibrary.setConfig({ 'url': 'URL' }); + it('results with config available with url and debounce', function () { + idImportlibrary.setConfig({ 'url': 'URL', 'debounce': 0 }); sinon.assert.called(utils.logInfo); }); + it('results with config debounce ', function () { + let config = { 'url': 'URL', 'debounce': 300 } + idImportlibrary.setConfig(config); + expect(config.debounce).to.be.equal(300); + }); + it('results with config default debounce ', function () { let config = { 'url': 'URL' } idImportlibrary.setConfig(config); expect(config.debounce).to.be.equal(250); }); it('results with config default fullscan ', function () { - let config = { 'url': 'URL' } + let config = { 'url': 'URL', 'debounce': 0 } idImportlibrary.setConfig(config); expect(config.fullscan).to.be.equal(false); }); it('results with config fullscan ', function () { - let config = { 'url': 'URL', 'fullscan': true } + let config = { 'url': 'URL', 'fullscan': true, 'debounce': 0 } + idImportlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(true); + expect(config.inputscan).to.be.equal(false); + }); + it('results with config inputscan ', function () { + let config = { 'inputscan': true, 'debounce': 0 } + idImportlibrary.setConfig(config); + expect(config.inputscan).to.be.equal(true); + }); + }); + describe('Test with email is found', function () { + let mutationObserverStub; + let userId; + let refreshUserIdSpy; + beforeEach(function() { + let sandbox = sinon.createSandbox(); + refreshUserIdSpy = sinon.spy(window.$$PREBID_GLOBAL$$, 'refreshUserIds'); + clock = sinon.useFakeTimers(1046952000000); // 2003-03-06T12:00:00Z + mutationObserverStub = sinon.stub(window, 'MutationObserver').returns(mockMutationObserver); + userId = sandbox.stub(window.$$PREBID_GLOBAL$$, 'getUserIds').returns({id: {'MOCKID': '1111'}}); + fakeServer.respondWith('POST', 'URL', [200, + { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + '' + ]); + }); + afterEach(function () { + sandbox.restore(); + clock.restore(); + userId.restore(); + refreshUserIdSpy.restore(); + mutationObserverStub.restore(); + document.body.innerHTML = ''; + }); + + it('results with config fullscan with email found in html ', function () { + document.body.innerHTML = '
test@test.com
'; + let config = { 'url': 'URL', 'fullscan': true, 'debounce': 0 } + idImportlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(true); + expect(config.inputscan).to.be.equal(false); + expect(refreshUserIdSpy.calledOnce).to.equal(true); + }); + + it('results with config fullscan with no email found in html ', function () { + document.body.innerHTML = '
test
'; + let config = { 'url': 'URL', 'fullscan': true, 'debounce': 0 } + idImportlibrary.setConfig(config); + expect(config.fullscan).to.be.equal(true); + expect(config.inputscan).to.be.equal(false); + expect(refreshUserIdSpy.calledOnce).to.equal(false); + }); + + it('results with config formElementId without listner ', function () { + let config = { url: 'testUrl', 'formElementId': 'userid', 'debounce': 0 } + document.body.innerHTML = ''; + idImportlibrary.setConfig(config); + expect(config.formElementId).to.be.equal('userid'); + expect(refreshUserIdSpy.calledOnce).to.equal(true); + }); + + it('results with config formElementId with listner ', function () { + let config = { url: 'testUrl', 'formElementId': 'userid', 'debounce': 0 } + document.body.innerHTML = ''; + idImportlibrary.setConfig(config); + expect(config.formElementId).to.be.equal('userid'); + expect(refreshUserIdSpy.calledOnce).to.equal(false); + }); + + it('results with config target without listner ', function () { + let config = { url: 'testUrl', 'target': 'userid', 'debounce': 0 } + document.body.innerHTML = '
test@test.com
'; + idImportlibrary.setConfig(config); + expect(config.target).to.be.equal('userid'); + expect(refreshUserIdSpy.calledOnce).to.equal(true); + }); + it('results with config target with listner ', function () { + let config = { url: 'testUrl', 'target': 'userid', 'debounce': 0 } + document.body.innerHTML = '
'; + idImportlibrary.setConfig(config); + + expect(config.target).to.be.equal('userid'); + expect(refreshUserIdSpy.calledOnce).to.equal(false); + }); + + it('results with config target with listner', function () { + let config = { url: 'testUrl', 'target': 'userid', 'debounce': 0 } + idImportlibrary.setConfig(config); + document.body.innerHTML = '
test@test.com
'; + expect(config.target).to.be.equal('userid'); + expect(refreshUserIdSpy.calledOnce).to.equal(false); + }); + it('results with config fullscan ', function () { + let config = { url: 'testUrl', 'fullscan': true, 'debounce': 0 } idImportlibrary.setConfig(config); + document.body.innerHTML = '
'; expect(config.fullscan).to.be.equal(true); + expect(refreshUserIdSpy.calledOnce).to.equal(false); + }); + it('results with config inputscan with listner', function () { + let config = { url: 'testUrl', 'inputscan': true, 'debounce': 0 } + var input = document.createElement('input'); + input.setAttribute('type', 'text'); + document.body.appendChild(input); + idImportlibrary.setConfig(config); + expect(config.inputscan).to.be.equal(true); + input.setAttribute('value', 'text@text.com'); + const inputEvent = new InputEvent('blur'); + input.dispatchEvent(inputEvent); + expect(refreshUserIdSpy.calledOnce).to.equal(true); + }); + + it('results with config inputscan with listner and no user ids ', function () { + let config = { 'url': 'testUrl', 'inputscan': true, 'debounce': 0 } + document.body.innerHTML = ''; + idImportlibrary.setConfig(config); + expect(config.inputscan).to.be.equal(true); + expect(refreshUserIdSpy.calledOnce).to.equal(false); + }); + + it('results with config inputscan with listner ', function () { + let config = { 'url': 'testUrl', 'inputscan': true, 'debounce': 0 } + document.body.innerHTML = ''; + idImportlibrary.setConfig(config); + expect(config.inputscan).to.be.equal(true); + expect(refreshUserIdSpy.calledOnce).to.equal(false); + }); + + it('results with config inputscan without listner ', function () { + let config = { 'url': 'testUrl', 'inputscan': true, 'debounce': 0 } + document.body.innerHTML = ''; + idImportlibrary.setConfig(config); + expect(config.inputscan).to.be.equal(true); + expect(refreshUserIdSpy.calledOnce).to.equal(true); + }); + }); + describe('Tests with no user ids', function () { + let mutationObserverStub; + let userId; + let jsonSpy; + beforeEach(function() { + let sandbox = sinon.createSandbox(); + clock = sinon.useFakeTimers(1046952000000); // 2003-03-06T12:00:00Z + mutationObserverStub = sinon.stub(window, 'MutationObserver'); + jsonSpy = sinon.spy(JSON, 'stringify'); + fakeServer.respondWith('POST', 'URL', [200, + { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*' + }, + '' + ]); + }); + afterEach(function () { + sandbox.restore(); + clock.restore(); + jsonSpy.restore(); + mutationObserverStub.restore(); + }); + it('results with config inputscan without listner with no user ids ', function () { + let config = { 'url': 'testUrl', 'inputscan': true, 'debounce': 0 } + document.body.innerHTML = ''; + idImportlibrary.setConfig(config); + expect(config.inputscan).to.be.equal(true); + expect(jsonSpy.calledOnce).to.equal(false); + }); + it('results with config inputscan without listner with no user ids ', function () { + let config = { 'url': 'testUrl', 'inputscan': true, 'debounce': 0 } + document.body.innerHTML = ''; + idImportlibrary.setConfig(config); + expect(config.inputscan).to.be.equal(true); + expect(jsonSpy.calledOnce).to.equal(false); }); }); }); diff --git a/test/spec/modules/imRtdProvider_spec.js b/test/spec/modules/imRtdProvider_spec.js index 58410dc0e38..e3365e8eaf2 100644 --- a/test/spec/modules/imRtdProvider_spec.js +++ b/test/spec/modules/imRtdProvider_spec.js @@ -1,6 +1,7 @@ import { imRtdSubmodule, storage, + getBidderFunction, getCustomBidderFunction, setRealTimeData, getRealTimeData, @@ -47,6 +48,30 @@ describe('imRtdProvider', function () { }) }) + describe('getBidderFunction', function () { + const assumedBidder = [ + 'ix', + 'pubmatic' + ]; + assumedBidder.forEach(bidderName => { + it(`should return bidderFunction with assumed bidder: ${bidderName}`, function () { + expect(getBidderFunction(bidderName)).to.exist.and.to.be.a('function'); + }); + + it(`should return bid with correct key data: ${bidderName}`, function () { + const bid = {bidder: bidderName}; + expect(getBidderFunction(bidderName)(bid, {'im_segments': ['12345', '67890']})).to.equal(bid); + }); + it(`should return bid without data: ${bidderName}`, function () { + const bid = {bidder: bidderName}; + expect(getBidderFunction(bidderName)(bid, '')).to.equal(bid); + }); + }); + it(`should return null with unexpected bidder`, function () { + expect(getBidderFunction('test')).to.equal(null); + }); + }) + describe('getCustomBidderFunction', function () { it('should return config function', function () { const config = { diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 095e50f0c66..7d6099e0de6 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -92,7 +92,8 @@ describe('Improve Digital Adapter Tests', function () { gdprConsent: { consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', vendorData: {}, - gdprApplies: true + gdprApplies: true, + addtlConsent: '1~1.35.41.101', }, }; @@ -278,6 +279,7 @@ describe('Improve Digital Adapter Tests', function () { const request = spec.buildRequests([bidRequest], bidderRequestGdpr)[0]; const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); expect(params.bid_request.gdpr).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(params.bid_request.user.ext.consented_providers_settings.consented_providers).to.exist.and.to.deep.equal([1, 35, 41, 101]); }); it('should add CCPA consent string', function () { @@ -513,6 +515,48 @@ describe('Improve Digital Adapter Tests', function () { }); getConfigStub.restore(); }); + + it('should set pagecat and genre âžž fpd:ortb2.site', function() { + config.setConfig(JSON.parse('{"ortb2":{"site":{"cat":["IAB2"],"pagecat":["IAB2-2"],"content":{"genre":"Adventure"}}}}')); + const bidRequest = Object.assign({}, simpleBidRequest); + const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.pagecat).to.be.an('array'); + expect(params.bid_request.pagecat).to.deep.equal(['IAB2-2']); + expect(params.bid_request.genre).to.be.a('string'); + expect(params.bid_request.genre).be.equal('Adventure'); + }); + + it('should not set pagecat and genre when malformed data provided âžž fpd:ortb2.site', function() { + config.setConfig(JSON.parse('{"ortb2":{"site":{"pagecat":"IAB2-2","content":{"genre":["Adventure"]}}}}')); + const bidRequest = Object.assign({}, simpleBidRequest); + const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.pagecat).does.not.exist; + expect(params.bid_request.genre).does.not.exist; + }); + + it('should use cat when pagecat not available âžž fpd:ortb2.site', function() { + config.setConfig(JSON.parse('{"ortb2":{"site":{"cat":["IAB2"]}}}')); + const bidRequest = Object.assign({}, simpleBidRequest); + const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.pagecat).to.be.an('array'); + expect(params.bid_request.pagecat).to.deep.equal(['IAB2']); + }); + + it('should format pagecat correctly âžž fpd:ortb2.site', function() { + config.setConfig(JSON.parse('{"ortb2":{"site":{"cat":["IAB2", ["IAB-1"], "IAB3", 123, ""]}}}')); + const bidRequest = Object.assign({}, simpleBidRequest); + const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.pagecat).to.be.an('array'); + expect(params.bid_request.pagecat).to.deep.equal([ + 'IAB2', + 'IAB3' + ] + ); + }); }); const serverResponse = { diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 305f39c13ac..7764117dbae 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec, storage } from '../../../modules/insticatorBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js' +import { userSync } from '../../../src/userSync.js'; const USER_ID_KEY = 'hb_insticator_uid'; const USER_ID_DUMMY_VALUE = '74f78609-a92d-4cf1-869f-1b244bbfb5d2'; @@ -11,12 +12,11 @@ let utils = require('src/utils.js'); describe('InsticatorBidAdapter', function () { const adapter = newBidder(spec); - const bidderRequestId = '22edbae2733bf6'; let bidRequest = { bidder: 'insticator', adUnitCode: 'adunit-code', params: { - adUnitId: '1a2b3c4d5e6f1a2b3c4d', + adUnitId: '1a2b3c4d5e6f1a2b3c4d' }, sizes: [[300, 250], [300, 600]], mediaTypes: { @@ -25,38 +25,10 @@ describe('InsticatorBidAdapter', function () { } }, bidId: '30b31c1838de1e', - ortb2Imp: { - ext: { - gpid: '1111/homepage' - } - }, - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'insticator.com', - sid: '00001', - hp: 1, - rid: bidderRequestId - } - ] - }, - userIdAsEids: [ - { - source: 'criteo.com', - uids: [ - { - id: '123', - atype: 1 - } - ] - } - ], }; let bidderRequest = { - bidderRequestId, + bidderRequestId: '22edbae2733bf6', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', timeout: 300, gdprConsent: { @@ -172,23 +144,12 @@ describe('InsticatorBidAdapter', function () { const data = JSON.parse(requests[0].data); expect(data).to.be.an('object'); - expect(data).to.have.all.keys('id', 'tmax', 'source', 'site', 'device', 'regs', 'user', 'imp', 'ext'); + expect(data).to.have.all.keys('id', 'tmax', 'source', 'site', 'device', 'regs', 'user', 'imp'); expect(data.id).to.equal(bidderRequest.bidderRequestId); expect(data.tmax).to.equal(bidderRequest.timeout); - expect(data.source).to.have.all.keys('fd', 'tid', 'ext'); - expect(data.source.fd).to.equal(1); - expect(data.source.tid).to.equal(bidderRequest.auctionId); - expect(data.source.ext).to.have.property('schain').to.deep.equal({ - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'insticator.com', - sid: '00001', - hp: 1, - rid: bidderRequest.bidderRequestId - } - ] + expect(data.source).to.eql({ + fd: 1, + tid: bidderRequest.auctionId, }); expect(data.site).to.be.an('object'); expect(data.site.domain).not.to.be.empty; @@ -206,18 +167,6 @@ describe('InsticatorBidAdapter', function () { expect(data.regs.ext.gdprConsentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.user).to.be.an('object'); expect(data.user.id).to.equal(USER_ID_DUMMY_VALUE); - expect(data.user.ext).to.have.property('eids'); - expect(data.user.ext.eids).to.deep.equal([ - { - source: 'criteo.com', - uids: [ - { - id: '123', - atype: 1 - } - ] - } - ]); expect(data.imp).to.be.an('array').that.have.lengthOf(1); expect(data.imp).to.deep.equal([{ id: bidRequest.bidId, @@ -229,20 +178,11 @@ describe('InsticatorBidAdapter', function () { ] }, ext: { - gpid: bidRequest.ortb2Imp.ext.gpid, insticator: { adUnitId: bidRequest.params.adUnitId, }, } }]); - expect(data.ext).to.be.an('object'); - expect(data.ext.insticator).to.be.an('object') - expect(data.ext.insticator).to.deep.equal({ - adapter: { - vendor: 'prebid', - prebid: '$prebid.version$' - } - }); }); it('should generate new userId if not valid user is stored', function () { @@ -341,12 +281,7 @@ describe('InsticatorBidAdapter', function () { h: 200, adm: 'adm1', exp: 60, - adomain: ['test1.com'], - ext: { - meta: { - test: 1 - } - } + bidADomain: ['test1.com'], }, { impid: 'bid2', @@ -355,7 +290,7 @@ describe('InsticatorBidAdapter', function () { w: 600, h: 200, adm: 'adm2', - adomain: ['test2.com'], + bidADomain: ['test2.com'], }, { impid: 'bid3', @@ -364,7 +299,7 @@ describe('InsticatorBidAdapter', function () { w: 300, h: 200, adm: 'adm3', - adomain: ['test3.com'], + bidADomain: ['test3.com'], } ], }, @@ -383,12 +318,13 @@ describe('InsticatorBidAdapter', function () { width: 300, height: 200, mediaType: 'banner', + meta: { + advertiserDomains: [ + 'test1.com' + ] + }, ad: 'adm1', adUnitCode: 'adunit-code-1', - meta: { - advertiserDomains: ['test1.com'], - test: 1 - } }, { requestId: 'bid2', diff --git a/test/spec/modules/intersectionRtdProvider_spec.js b/test/spec/modules/intersectionRtdProvider_spec.js new file mode 100644 index 00000000000..eb223d81d8f --- /dev/null +++ b/test/spec/modules/intersectionRtdProvider_spec.js @@ -0,0 +1,141 @@ +import {config as _config, config} from 'src/config.js'; +import { expect } from 'chai'; +import events from 'src/events.js'; +import * as prebidGlobal from 'src/prebidGlobal.js'; +import { intersectionSubmodule } from 'modules/intersectionRtdProvider.js'; +import * as utils from 'src/utils.js'; +import {getGlobal} from 'src/prebidGlobal.js'; +import 'src/prebid.js'; + +describe('Intersection RTD Provider', function () { + let sandbox; + let placeholder; + const pbjs = getGlobal(); + const adUnit = { + code: 'ad-slot-1', + mediaTypes: { + banner: { + sizes: [ [300, 250] ] + } + }, + bids: [ + { + bidder: 'fake' + } + ] + }; + const providerConfig = {name: 'intersection', waitForIt: true}; + const rtdConfig = {realTimeData: {auctionDelay: 200, dataProviders: [providerConfig]}} + describe('IntersectionObserver not supported', function() { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + sandbox = undefined; + }); + it('init should return false', function () { + sandbox.stub(window, 'IntersectionObserver').value(undefined); + expect(intersectionSubmodule.init({})).is.false; + }); + }); + describe('IntersectionObserver supported', function() { + beforeEach(function() { + sandbox = sinon.sandbox.create(); + placeholder = createDiv(); + append(); + const __config = {}; + sandbox.stub(_config, 'getConfig').callsFake(function (path) { + return utils.deepAccess(__config, path); + }); + sandbox.stub(_config, 'setConfig').callsFake(function (obj) { + utils.mergeDeep(__config, obj); + }); + }); + afterEach(function() { + sandbox.restore(); + remove(); + sandbox = undefined; + placeholder = undefined; + pbjs.removeAdUnit(); + }); + it('init should return true', function () { + expect(intersectionSubmodule.init({})).is.true; + }); + it('should set intersection. (request with "adUnitCodes")', function(done) { + pbjs.addAdUnits([utils.deepClone(adUnit)]); + config.setConfig(rtdConfig); + const onDone = sandbox.stub(); + const requestBidObject = {adUnitCodes: [adUnit.code]}; + intersectionSubmodule.init({}); + intersectionSubmodule.getBidRequestData( + requestBidObject, + onDone, + providerConfig + ); + setTimeout(function() { + expect(pbjs.adUnits[0].bids[0]).to.have.property('intersection'); + done(); + }, 200); + }); + it('should set intersection. (request with "adUnits")', function(done) { + config.setConfig(rtdConfig); + const onDone = sandbox.stub(); + const requestBidObject = {adUnits: [utils.deepClone(adUnit)]}; + intersectionSubmodule.init(); + intersectionSubmodule.getBidRequestData( + requestBidObject, + onDone, + providerConfig + ); + setTimeout(function() { + expect(requestBidObject.adUnits[0].bids[0]).to.have.property('intersection'); + done(); + }, 200); + }); + it('should set intersection. (request all)', function(done) { + pbjs.addAdUnits([utils.deepClone(adUnit)]); + config.setConfig(rtdConfig); + const onDone = sandbox.stub(); + const requestBidObject = {}; + intersectionSubmodule.init({}); + intersectionSubmodule.getBidRequestData( + requestBidObject, + onDone, + providerConfig + ); + setTimeout(function() { + expect(pbjs.adUnits[0].bids[0]).to.have.property('intersection'); + done(); + }, 200); + }); + it('should call done due timeout', function(done) { + config.setConfig(rtdConfig); + remove(); + const onDone = sandbox.stub(); + const requestBidObject = {adUnits: [utils.deepClone(adUnit)]}; + intersectionSubmodule.init({}); + intersectionSubmodule.getBidRequestData( + requestBidObject, + onDone, + {...providerConfig, test: 1} + ); + setTimeout(function() { + sinon.assert.calledOnce(onDone); + expect(requestBidObject.adUnits[0].bids[0]).to.not.have.property('intersection'); + done(); + }, 300); + }); + }); + function createDiv() { + const div = document.createElement('div'); + div.id = adUnit.code; + return div; + } + function append() { + placeholder && document.body.appendChild(placeholder); + } + function remove() { + placeholder && placeholder.parentElement && placeholder.parentElement.removeChild(placeholder); + } +}); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 8b92e0ee81b..ae7b30c9f81 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -15,7 +15,7 @@ describe('invibesBidAdapter:', function () { params: { placementId: PLACEMENT_ID }, - adUnitCode: 'test-div', + adUnitCode: 'test-div1', auctionId: 'a1', sizes: [ [300, 250], @@ -30,7 +30,7 @@ describe('invibesBidAdapter:', function () { params: { placementId: 'abcde' }, - adUnitCode: 'test-div', + adUnitCode: 'test-div2', auctionId: 'a2', sizes: [ [300, 250], @@ -48,7 +48,7 @@ describe('invibesBidAdapter:', function () { params: { placementId: PLACEMENT_ID }, - adUnitCode: 'test-div', + adUnitCode: 'test-div1', auctionId: 'a1', sizes: [ [300, 250], @@ -67,7 +67,7 @@ describe('invibesBidAdapter:', function () { params: { placementId: 'abcde' }, - adUnitCode: 'test-div', + adUnitCode: 'test-div2', auctionId: 'a2', sizes: [ [300, 250], @@ -158,6 +158,125 @@ describe('invibesBidAdapter:', function () { expect(request.method).to.equal('GET'); }); + it('sends bid request to custom endpoint via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'placement', + customEndpoint: 'sub.domain.com/Bid/VideoAdContent' + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('sub.domain.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to default endpoint when no placement', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to default endpoint when null placement', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: null + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to default endpoint 1 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'placement' + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to network id endpoint 1 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'placement', + domainId: 1001 + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to network id endpoint 2 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'placement', + domainId: 1002 + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid2.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to network id by placement 1 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'infeed_ivbs1' + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to network id by placement 2 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'infeed_ivbs2' + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid2.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + + it('sends bid request to network id by placement 10 via GET', function () { + const request = spec.buildRequests([{ + bidId: 'b1', + bidder: BIDDER_CODE, + params: { + placementId: 'infeed_ivbs10' + }, + adUnitCode: 'test-div1' + }]); + expect(request.url).to.equal('https://bid10.videostep.com/Bid/VideoAdContent'); + expect(request.method).to.equal('GET'); + }); + it('sends cookies with the bid request', function () { const request = spec.buildRequests(bidRequests); expect(request.options.withCredentials).to.equal(true); @@ -223,6 +342,12 @@ describe('invibesBidAdapter:', function () { expect(JSON.parse(request.data.bidParamsJson).placementIds).to.contain(bidRequests[1].params.placementId); }); + it('sends all adUnitCodes', function () { + const request = spec.buildRequests(bidRequests); + expect(JSON.parse(request.data.bidParamsJson).adUnitCodes).to.contain(bidRequests[0].adUnitCode); + expect(JSON.parse(request.data.bidParamsJson).adUnitCodes).to.contain(bidRequests[1].adUnitCode); + }); + it('sends all Placement Ids and userId', function () { const request = spec.buildRequests(bidRequestsWithUserId); expect(JSON.parse(request.data.bidParamsJson).userId).to.exist; @@ -823,6 +948,20 @@ describe('invibesBidAdapter:', function () { } }; + let responseWithAdUnit = { + Ads: [{ + BidPrice: 0.5, + VideoExposedId: 123 + }], + BidModel: { + BidVersion: 1, + PlacementId: '12345_test-div1', + AuctionStartTime: Date.now(), + CreativeHtml: '' + }, + UseAdUnitCode: true + }; + var buildResponse = function(placementId, cid, blcids, creativeId) { return { MultipositionEnabled: true, @@ -911,6 +1050,11 @@ describe('invibesBidAdapter:', function () { let secondResult = spec.interpretResponse({body: response}, {bidRequests}); expect(secondResult).to.be.empty; }); + + it('bids using the adUnitCode', function () { + let result = spec.interpretResponse({body: responseWithAdUnit}, {bidRequests}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); }); context('when the response has meta', function () { diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 59eb9e76c9a..cdf70a799e3 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -2,8 +2,9 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { spec } from 'modules/ixBidAdapter.js'; +import { spec, storage, ERROR_CODES } from '../../../modules/ixBidAdapter.js'; import { createEidsArray } from 'modules/userId/eids.js'; +import { deepAccess } from '../../../src/utils.js'; describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/cygnus'; @@ -353,6 +354,37 @@ describe('IndexexchangeAdapter', function () { ] }; + const DEFAULT_VIDEO_BID_RESPONSE_WITH_MTYPE_SET = { + cur: 'USD', + id: '1aa2bb3cc4de', + seatbid: [ + { + bid: [ + { + crid: '12346', + adomain: ['www.abcd.com'], + adid: '14851456', + impid: '1a2b3c4e', + cid: '3051267', + price: 110, + id: '2', + mtype: 2, + adm: ' Test In-Stream Video { + expect(imp.ext.siteID).to.be.a('string'); + }); + }); + describe('build requests with price floors', () => { const highFloor = 4.5; const lowFloor = 3.5; @@ -1607,7 +1680,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.banner.format[0].w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); expect(impression.banner.format[0].h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); expect(impression.banner.topframe).to.be.oneOf([0, 1]); - expect(impression.banner.format[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(impression.banner.format[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); expect(impression.banner.format[0].ext.sid).to.equal('50'); }); @@ -1621,7 +1694,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.banner.format[0].w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); expect(impression.banner.format[0].h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); expect(impression.banner.topframe).to.be.oneOf([0, 1]); - expect(impression.banner.format[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(impression.banner.format[0].ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); expect(impression.banner.format[0].ext.sid).to.equal('abc'); }); @@ -1719,7 +1792,7 @@ describe('IndexexchangeAdapter', function () { expect(w).to.equal(size[0]); expect(h).to.equal(size[1]); - expect(ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId.toString()); + expect(ext.siteID).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); expect(ext.sid).to.equal(sidValue); }); }); @@ -1884,7 +1957,7 @@ describe('IndexexchangeAdapter', function () { expect(w).to.equal(size[0]); expect(h).to.equal(size[1]); - expect(ext.siteID).to.equal(bids[impressionIndex].params.siteId.toString()); + expect(ext.siteID).to.equal(bids[impressionIndex].params.siteId); expect(ext.sid).to.equal(sidValue); }); }); @@ -2088,7 +2161,7 @@ describe('IndexexchangeAdapter', function () { expect(w).to.equal(size[0]); expect(h).to.equal(size[1]); - expect(ext.siteID).to.equal(bid.params.siteId.toString()); + expect(ext.siteID).to.equal(bid.params.siteId); expect(ext.sid).to.equal(sidValue); }); }); @@ -2343,6 +2416,45 @@ describe('IndexexchangeAdapter', function () { expect(result[0]).to.deep.equal(expectedParse[0]); }); + it('should get correct bid response for video ad and set bid.vastXml when mtype is 2 (video)', function () { + const expectedParse = [ + { + requestId: '1a2b3c4e', + cpm: 1.1, + creativeId: '12346', + mediaType: 'video', + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [ + 400, + 100 + ] + ] + } + }, + width: 640, + height: 480, + currency: 'USD', + ttl: 3600, + netRevenue: true, + vastXml: ' Test In-Stream Video { + let TODAY = new Date().toISOString().slice(0, 10); + const key = 'ixdiag'; + + let sandbox; + let setDataInLocalStorageStub; + let getDataFromLocalStorageStub; + let removeDataFromLocalStorageStub; + let localStorageValues = {}; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + setDataInLocalStorageStub = sandbox.stub(storage, 'setDataInLocalStorage').callsFake((key, value) => localStorageValues[key] = value) + getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => localStorageValues[key]) + removeDataFromLocalStorageStub = sandbox.stub(storage, 'removeDataFromLocalStorage').callsFake((key) => delete localStorageValues[key]) + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + }); + + afterEach(() => { + setDataInLocalStorageStub.restore(); + getDataFromLocalStorageStub.restore(); + removeDataFromLocalStorageStub.restore(); + localStorageValues = {}; + sandbox.restore(); + + config.setConfig({ + fpd: {}, + ix: {}, + }) + }); + + it('should not log error in LocalStorage when there is no logError called.', () => { + const bid = DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]; + expect(spec.isBidRequestValid(bid)).to.be.true; + expect(localStorageValues[key]).to.be.undefined; + }); + + it('should log ERROR_CODES.BID_SIZE_INVALID_FORMAT in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = ['400', 100]; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1 } }); + }); + + it('should log ERROR_CODES.BID_SIZE_NOT_INCLUDED in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = [407, 100]; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_NOT_INCLUDED]: 1 } }); + }); + + it('should log ERROR_CODES.PROPERTY_NOT_INCLUDED in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.video = {}; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 4 } }); + }); + + it('should log ERROR_CODES.SITE_ID_INVALID_VALUE in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.siteId = false; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.SITE_ID_INVALID_VALUE]: 1 } }); + }); + + it('should log ERROR_CODES.BID_FLOOR_INVALID_FORMAT in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.bidFloor = true; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_FLOOR_INVALID_FORMAT]: 1 } }); + }); + + it('should log ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + + config.setConfig({ + ix: { + firstPartyData: { + cd: Array(1700).join('#') + } + } + }); + + expect(spec.isBidRequestValid(bid)).to.be.true; + spec.buildRequests([bid]); + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.IX_FPD_EXCEEDS_MAX_SIZE]: 2 } }); + }); + + it('should log ERROR_CODES.EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.bidderRequestId = Array(8000).join('#'); + + expect(spec.isBidRequestValid(bid)).to.be.true; + spec.buildRequests([bid]); + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.EXCEEDS_MAX_SIZE]: 2 } }); + }); + + it('should log ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + + config.setConfig({ + fpd: { + site: { + data: { + pageType: Array(5700).join('#') + } + } + } + }); + + expect(spec.isBidRequestValid(bid)).to.be.true; + spec.buildRequests([bid]); + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PB_FPD_EXCEEDS_MAX_SIZE]: 2 } }); + }); + + it('should log ERROR_CODES.VIDEO_DURATION_INVALID in LocalStorage when there is logError called.', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.video.minduration = 1; + bid.params.video.maxduration = 0; + + expect(spec.isBidRequestValid(bid)).to.be.true; + spec.buildRequests([bid]); + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.VIDEO_DURATION_INVALID]: 2 } }); + }); + + it('should increment errors for errorCode', () => { + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.video = {}; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 4 } }); + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 8 } }); + }); + + it('should add new errorCode to ixdiag.', () => { + let bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = ['400', 100]; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1 } }); + + bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.siteId = false; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ + [TODAY]: { + [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1, + [ERROR_CODES.SITE_ID_INVALID_VALUE]: 1 + } + }); + }); + + it('should clear errors with successful response', () => { + const ixdiag = { [TODAY]: { '1': 1, '3': 8, '4': 1 } }; + setDataInLocalStorageStub(key, JSON.stringify(ixdiag)); + + expect(JSON.parse(localStorageValues[key])).to.deep.equal(ixdiag); + + const request = DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]; + expect(spec.isBidRequestValid(request)).to.be.true; + + const data = { + ...utils.deepClone(DEFAULT_BIDDER_REQUEST_DATA[0]), + r: JSON.stringify({ + id: '345', + imp: [ + { + id: '1a2b3c4e', + } + ], + ext: { + ixdiag: { + err: { + '4': 8 + } + } + } + }), + }; + + const validBidRequests = [ + DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0], + DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0] + ]; + + spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data, validBidRequests }); + + expect(localStorageValues[key]).to.be.undefined; + }); + + it('should clear errors after 7 day expiry errorCode', () => { + const EXPIRED_DATE = '2019-12-12'; + + const ixdiag = { [EXPIRED_DATE]: { '1': 1, '3': 8, '4': 1 }, [TODAY]: { '3': 8, '4': 1 } }; + setDataInLocalStorageStub(key, JSON.stringify(ixdiag)); + + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = ['400', 100]; + + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(JSON.parse(localStorageValues[key])[EXPIRED_DATE]).to.be.undefined; + expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { '1': 1, '3': 8, '4': 1 } }) + }); + + it('should not save error data into localstorage if consent is not given', () => { + config.setConfig({ deviceAccess: false }); + const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); + bid.params.size = ['400', 100]; + expect(spec.isBidRequestValid(bid)).to.be.false; + expect(localStorageValues[key]).to.be.undefined; + }); + }); }); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 458e45e8ae7..db17d18864b 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -1,7 +1,8 @@ import { fetchTargetingForMediaId, getVatFromCache, extractPublisherParams, formatTargetingResponse, getVatFromPlayer, enrichAdUnits, addTargetingToBid, - fetchTargetingInformation, jwplayerSubmodule } from 'modules/jwplayerRtdProvider.js'; + fetchTargetingInformation, jwplayerSubmodule, getContentId, getContentData } from 'modules/jwplayerRtdProvider.js'; import { server } from 'test/mocks/xhr.js'; +import {addOrtbSiteContent} from '../../../modules/jwplayerRtdProvider'; describe('jwplayerRtdProvider', function() { const testIdForSuccess = 'test_id_for_success'; @@ -412,7 +413,7 @@ describe('jwplayerRtdProvider', function() { }); }); - describe(' Extract Publisher Params', function () { + describe('Extract Publisher Params', function () { const config = { mediaID: 'test' }; it('should exclude adUnits that do not support instream video and do not specify jwTargeting', function () { @@ -480,6 +481,198 @@ describe('jwplayerRtdProvider', function() { }) }); + describe('Get content id', function() { + it('prefixes jw_ to the media id', function () { + const mediaId = 'mediaId'; + const contentId = getContentId(mediaId); + expect(contentId).to.equal('jw_mediaId'); + }); + + it('returns undefined when media id is empty', function () { + let contentId = getContentId(); + expect(contentId).to.be.undefined; + contentId = getContentId(''); + expect(contentId).to.be.undefined; + contentId = getContentId(null); + expect(contentId).to.be.undefined; + }); + }); + + describe('Get Content Data', function () { + it('returns undefined when segments are empty', function () { + let data = getContentData(null); + expect(data).to.be.undefined; + data = getContentData(undefined); + expect(data).to.be.undefined; + data = getContentData([]); + expect(data).to.be.undefined; + }); + + it('returns proper format', function () { + const segment1 = 'segment1'; + const segment2 = 'segment2'; + const segment3 = 'segment3'; + const data = getContentData([segment1, segment2, segment3]); + expect(data).to.have.property('name', 'jwplayer'); + expect(data.ext).to.have.property('segtax', 502); + expect(data.segment[0]).to.deep.equal({ id: segment1, value: segment1 }); + expect(data.segment[1]).to.deep.equal({ id: segment2, value: segment2 }); + expect(data.segment[2]).to.deep.equal({ id: segment3, value: segment3 }); + }); + }); + + describe(' Add Ortb Site Content', function () { + it('should maintain object structure when id and data params are empty', function () { + const bid = { + ortb2: { + site: { + content: { + id: 'randomId' + }, + random: { + random_sub: 'randomSub' + } + }, + app: { + content: { + id: 'appId' + } + } + } + }; + addOrtbSiteContent(bid); + expect(bid).to.have.nested.property('ortb2.site.content.id', 'randomId'); + expect(bid).to.have.nested.property('ortb2.site.random.random_sub', 'randomSub'); + expect(bid).to.have.nested.property('ortb2.app.content.id', 'appId'); + }); + + it('should create a structure compliant with the oRTB 2 spec', function() { + const bid = {}; + const expectedId = 'expectedId'; + const expectedData = { datum: 'datum' }; + addOrtbSiteContent(bid, expectedId, expectedData); + expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId); + expect(bid).to.have.nested.property('ortb2.site.content.data'); + expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData); + }); + + it('should respect existing structure when adding adding fields', function () { + const bid = { + ortb2: { + site: { + content: { + id: 'oldId' + }, + random: { + random_sub: 'randomSub' + } + }, + app: { + content: { + id: 'appId' + } + } + } + }; + + const expectedId = 'expectedId'; + const expectedData = { datum: 'datum' }; + addOrtbSiteContent(bid, expectedId, expectedData); + expect(bid).to.have.nested.property('ortb2.site.random.random_sub', 'randomSub'); + expect(bid).to.have.nested.property('ortb2.app.content.id', 'appId'); + expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId); + expect(bid).to.have.nested.property('ortb2.site.content.data'); + expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData); + }); + + it('should set content id', function () { + const bid = {}; + const expectedId = 'expectedId'; + addOrtbSiteContent(bid, expectedId); + expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId); + }); + + it('should override content id', function () { + const bid = { + ortb2: { + site: { + content: { + id: 'oldId' + } + } + } + }; + + const expectedId = 'expectedId'; + addOrtbSiteContent(bid, expectedId); + expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId); + }); + + it('should keep previous content id when not set', function () { + const previousId = 'oldId'; + const bid = { + ortb2: { + site: { + content: { + id: previousId, + data: [{ datum: 'first_datum' }] + } + } + } + }; + + addOrtbSiteContent(bid, null, { datum: 'new_datum' }); + expect(bid).to.have.nested.property('ortb2.site.content.id', previousId); + }); + + it('should set content data', function () { + const bid = {}; + const expectedData = { datum: 'datum' }; + addOrtbSiteContent(bid, null, expectedData); + expect(bid).to.have.nested.property('ortb2.site.content.data'); + expect(bid.ortb2.site.content.data).to.have.length(1); + expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData); + }); + + it('should append content data', function () { + const bid = { + ortb2: { + site: { + content: { + data: [{ datum: 'first_datum' }] + } + } + } + }; + + const expectedData = { datum: 'datum' }; + addOrtbSiteContent(bid, null, expectedData); + expect(bid).to.have.nested.property('ortb2.site.content.data'); + expect(bid.ortb2.site.content.data).to.have.length(2); + expect(bid.ortb2.site.content.data.pop()).to.be.deep.equal(expectedData); + }); + + it('should keep previous data when not set', function () { + const expectedId = 'expectedId'; + const expectedData = { datum: 'first_datum' }; + const bid = { + ortb2: { + site: { + content: { + data: [expectedData] + } + } + } + }; + + addOrtbSiteContent(bid, expectedId); + expect(bid).to.have.nested.property('ortb2.site.content.data'); + expect(bid.ortb2.site.content.data).to.have.length(1); + expect(bid.ortb2.site.content.data[0]).to.be.deep.equal(expectedData); + expect(bid).to.have.nested.property('ortb2.site.content.id', expectedId); + }); + }); + describe('Add Targeting to Bid', function () { const targeting = {foo: 'bar'}; diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 43968bbef5a..1eb514e87d2 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -484,7 +484,7 @@ describe('kargo adapter tests', function () { height: 250, targetingCustom: 'dmpmptest1234', metadata: { - landingPageDomain: 'https://foobar.com' + landingPageDomain: ['https://foobar.com'] } }, 3: { diff --git a/test/spec/modules/konduitWrapper_spec.js b/test/spec/modules/konduitWrapper_spec.js index c88287c7066..506d2189049 100644 --- a/test/spec/modules/konduitWrapper_spec.js +++ b/test/spec/modules/konduitWrapper_spec.js @@ -53,7 +53,6 @@ describe('The Konduit vast wrapper module', function () { const callback = sinon.spy(); processBids({ bid, callback }); server.respond(); - expect(server.requests.length).to.equal(1); const requestBody = JSON.parse(server.requests[0].requestBody); diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 1a33bbe6cb9..191c4759f76 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -9,7 +9,8 @@ describe('limelightDigitalAdapter', function () { params: { host: 'exchange.ortb.net', adUnitId: 123, - adUnitType: 'banner' + adUnitType: 'banner', + publisherId: 'perfectPublisher' }, placementCode: 'placement_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', @@ -18,7 +19,17 @@ describe('limelightDigitalAdapter', function () { sizes: [[300, 250]] } }, - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + userIdAsEids: [ + { + source: 'test1.org', + uids: [ + { + id: '123', + } + ] + } + ] } const bid2 = { bidId: '58ee9870c3164a', @@ -32,7 +43,17 @@ describe('limelightDigitalAdapter', function () { placementCode: 'placement_1', auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', sizes: [[350, 200]], - transactionId: '068867d1-46ec-40bb-9fa0-e24611786fb4' + transactionId: '068867d1-46ec-40bb-9fa0-e24611786fb4', + userIdAsEids: [ + { + source: 'test2.org', + uids: [ + { + id: '234', + } + ] + } + ] } const bid3 = { bidId: '019645c7d69460', @@ -41,12 +62,26 @@ describe('limelightDigitalAdapter', function () { params: { host: 'exchange.ortb.net', adUnitId: 789, - adUnitType: 'video' + adUnitType: 'video', + publisherId: 'secondPerfectPublisher' }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', sizes: [[800, 600]], - transactionId: '738d5915-6651-43b9-9b6b-d50517350917' + transactionId: '738d5915-6651-43b9-9b6b-d50517350917', + userIdAsEids: [ + { + source: 'test3.org', + uids: [ + { + id: '345', + }, + { + id: '456', + } + ] + } + ] } const bid4 = { bidId: '019645c7d69460', @@ -62,7 +97,17 @@ describe('limelightDigitalAdapter', function () { video: { playerSize: [800, 600] }, - transactionId: '738d5915-6651-43b9-9b6b-d50517350917' + transactionId: '738d5915-6651-43b9-9b6b-d50517350917', + userIdAsEids: [ + { + source: 'test.org', + uids: [ + { + id: '111', + } + ] + } + ] } describe('buildRequests', function () { @@ -82,19 +127,33 @@ describe('limelightDigitalAdapter', function () { expect(serverRequest.method).to.equal('POST') }) it('Returns valid data if array of bids is valid', function () { - let data = serverRequest.data - expect(data).to.be.an('object') - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'secure', 'adUnits') - expect(data.deviceWidth).to.be.a('number') - expect(data.deviceHeight).to.be.a('number') - expect(data.secure).to.be.a('boolean') + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'secure', + 'adUnits' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.secure).to.be.a('boolean'); data.adUnits.forEach(adUnit => { - expect(adUnit).to.have.all.keys('id', 'bidId', 'type', 'sizes', 'transactionId') - expect(adUnit.id).to.be.a('number') - expect(adUnit.bidId).to.be.a('string') - expect(adUnit.type).to.be.a('string') - expect(adUnit.transactionId).to.be.a('string') - expect(adUnit.sizes).to.be.an('array') + expect(adUnit).to.have.all.keys( + 'id', + 'bidId', + 'type', + 'sizes', + 'transactionId', + 'publisherId', + 'userIdAsEids' + ); + expect(adUnit.id).to.be.a('number'); + expect(adUnit.bidId).to.be.a('string'); + expect(adUnit.type).to.be.a('string'); + expect(adUnit.transactionId).to.be.a('string'); + expect(adUnit.sizes).to.be.an('array'); + expect(adUnit.userIdAsEids).to.be.an('array'); }) }) }) @@ -192,7 +251,7 @@ describe('limelightDigitalAdapter', function () { expect(dataItem.meta.advertiserDomains).to.be.an('array'); expect(dataItem.meta.mediaType).to.be.a('string'); } - it('Returns an empty array if invalid response is passed', function () { + it('should return an empty array if invalid response is passed', function () { serverResponses = spec.interpretResponse('invalid_response'); expect(serverResponses).to.be.an('array').that.is.empty; }); @@ -456,10 +515,10 @@ describe('limelightDigitalAdapter', function () { }); function validateAdUnit(adUnit, bid) { - expect(adUnit.id).to.equal(bid.params.adUnitId) - expect(adUnit.bidId).to.equal(bid.bidId) - expect(adUnit.type).to.equal(bid.params.adUnitType.toUpperCase()) - expect(adUnit.transactionId).to.equal(bid.transactionId) + expect(adUnit.id).to.equal(bid.params.adUnitId); + expect(adUnit.bidId).to.equal(bid.bidId); + expect(adUnit.type).to.equal(bid.params.adUnitType.toUpperCase()); + expect(adUnit.transactionId).to.equal(bid.transactionId); let bidSizes = []; if (bid.mediaTypes) { if (bid.mediaTypes.video && bid.mediaTypes.video.playerSize) { @@ -478,4 +537,6 @@ function validateAdUnit(adUnit, bid) { height: size[1] } })); + expect(adUnit.publisherId).to.equal(bid.params.publisherId); + expect(adUnit.userIdAsEids).to.deep.equal(bid.userIdAsEids); } diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index 3e568c1175d..bd6f361572b 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -552,6 +552,28 @@ describe('Livewrapped analytics adapter', function () { expect(message.wins[0].floor).to.equal(1.1); expect(message.wins[1].floor).to.equal(2.2); }); + + it('should forward runner-up data as given by Livewrapped wrapper', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_WON, Object.assign({}, + MOCK.BID_WON[0], + { + 'rUp': 'rUpObject' + })); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + expect(message.wins.length).to.equal(1); + expect(message.wins[0].rUp).to.equal('rUpObject'); + }); }); describe('when given other endpoint', function () { diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index 085848cb97d..fac1ae1d6a0 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -885,6 +885,37 @@ describe('Livewrapped adapter tests', function () { expect(data.rtbData.user.ext.eids).to.deep.equal(testbidRequest.bids[0].userIdAsEids); }); + it('should merge user ids with existing ortb2', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + + let origGetConfig = config.getConfig; + sandbox.stub(config, 'getConfig').callsFake(function (key) { + if (key === 'ortb2') { + return {user: {ext: {prop: 'value'}}}; + } + return origGetConfig.apply(config, arguments); + }); + + let testbidRequest = clone(bidderRequest); + delete testbidRequest.bids[0].params.userId; + testbidRequest.bids[0].userIdAsEids = [ + { + 'source': 'pubcid.org', + 'uids': [{ + 'id': 'publisher-common-id', + 'atype': 1 + }] + } + ]; + + let result = spec.buildRequests(testbidRequest.bids, testbidRequest); + let data = JSON.parse(result.data); + var expected = {user: {ext: {prop: 'value', eids: testbidRequest.bids[0].userIdAsEids}}} + + expect(data.rtbData).to.deep.equal(expected); + }); + it('should send schain object if available', function() { sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); diff --git a/test/spec/modules/loglyliftBidAdapter_spec.js b/test/spec/modules/loglyliftBidAdapter_spec.js new file mode 100644 index 00000000000..6a68ef856ff --- /dev/null +++ b/test/spec/modules/loglyliftBidAdapter_spec.js @@ -0,0 +1,172 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/loglyliftBidAdapter'; +import * as utils from 'src/utils.js'; + +describe('loglyliftBidAdapter', function () { + const nativeBidRequests = [{ + bidder: 'loglylift', + bidId: '254304ac29e265', + params: { + adspotId: 16 + }, + adUnitCode: '/19968336/prebid_native_example_1', + transactionId: '10aee457-617c-4572-ab5b-99df1d73ccb4', + sizes: [ + [] + ], + bidderRequestId: '15da3afd9632d7', + auctionId: 'f890b7d9-e787-4237-ac21-6d8554abac9f', + mediaTypes: { + native: { + body: { + required: true + }, + icon: { + required: false + }, + title: { + required: true + }, + image: { + required: true + }, + sponsoredBy: { + required: true + } + } + } + }]; + + const bidderRequest = { + refererInfo: { + referer: 'fakeReferer', + reachedTop: true, + numIframes: 1, + stack: [] + }, + auctionStart: 1632194172781, + bidderCode: 'loglylift', + bidderRequestId: '15da3afd9632d7', + auctionId: 'f890b7d9-e787-4237-ac21-6d8554abac9f', + timeout: 3000 + }; + + const nativeServerResponse = { + body: { + bids: [{ + requestId: '254304ac29e265', + cpm: 10.123, + width: 360, + height: 360, + creativeId: '123456789', + currency: 'JPY', + netRevenue: true, + ttl: 30, + meta: { + advertiserDomains: ['advertiserexample.com'] + }, + native: { + clickUrl: 'https://dsp.logly.co.jp/click?ad=EXAMPECLICKURL', + image: { + url: 'https://cdn.logly.co.jp/images/000/194/300/normal.jpg', + width: '360', + height: '360' + }, + impressionTrackers: [ + 'https://b.logly.co.jp/sorry.html' + ], + sponsoredBy: 'logly', + title: 'Native Title', + } + }], + } + }; + + describe('isBidRequestValid', function () { + it('should return true if the adspotId parameter is present', function () { + expect(spec.isBidRequestValid(nativeBidRequests[0])).to.be.true; + }); + + it('should return false if the adspotId parameter is not present', function () { + let bidRequest = utils.deepClone(nativeBidRequests[0]); + delete bidRequest.params.adspotId; + expect(spec.isBidRequestValid(bidRequest)).to.be.false; + }); + }); + + describe('buildRequests', function () { + it('should generate a valid single POST request for multiple bid requests', function () { + const request = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bid.logly.co.jp/prebid/client/v1?adspot_id=16'); + expect(request.data).to.exist; + + const data = JSON.parse(request.data); + expect(data.auctionId).to.equal(nativeBidRequests[0].auctionId); + expect(data.bidderRequestId).to.equal(nativeBidRequests[0].bidderRequestId); + expect(data.transactionId).to.equal(nativeBidRequests[0].transactionId); + expect(data.adUnitCode).to.equal(nativeBidRequests[0].adUnitCode); + expect(data.bidId).to.equal(nativeBidRequests[0].bidId); + expect(data.mediaTypes).to.deep.equal(nativeBidRequests[0].mediaTypes); + expect(data.params).to.deep.equal(nativeBidRequests[0].params); + expect(data.prebidJsVersion).to.equal('$prebid.version$'); + expect(data.url).to.exist; + expect(data.domain).to.exist; + expect(data.referer).to.equal(bidderRequest.refererInfo.referer); + expect(data.auctionStartTime).to.equal(bidderRequest.auctionStart); + expect(data.currency).to.exist; + expect(data.timeout).to.equal(bidderRequest.timeout); + }); + }); + + describe('interpretResponse', function () { + it('should return an empty array if an invalid response is passed', function () { + const interpretedResponse = spec.interpretResponse({}, {}); + expect(interpretedResponse).to.be.an('array').that.is.empty; + }); + + it('should return valid response when passed valid server response', function () { + const request = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; + const interpretedResponse = spec.interpretResponse(nativeServerResponse, request); + + expect(interpretedResponse).to.have.lengthOf(1); + expect(interpretedResponse[0].cpm).to.equal(nativeServerResponse.body.bids[0].cpm); + expect(interpretedResponse[0].width).to.equal(nativeServerResponse.body.bids[0].width); + expect(interpretedResponse[0].height).to.equal(nativeServerResponse.body.bids[0].height); + expect(interpretedResponse[0].creativeId).to.equal(nativeServerResponse.body.bids[0].creativeId); + expect(interpretedResponse[0].currency).to.equal(nativeServerResponse.body.bids[0].currency); + expect(interpretedResponse[0].netRevenue).to.equal(nativeServerResponse.body.bids[0].netRevenue); + expect(interpretedResponse[0].ttl).to.equal(nativeServerResponse.body.bids[0].ttl); + expect(interpretedResponse[0].native).to.deep.equal(nativeServerResponse.body.bids[0].native); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal(nativeServerResponse.body.bids[0].meta.advertiserDomains[0]); + }); + }); + + describe('getUserSync tests', function () { + it('UserSync test : check type = iframe, check usermatch URL', function () { + const syncOptions = { + 'iframeEnabled': true + } + let userSync = spec.getUserSyncs(syncOptions, [nativeServerResponse]); + expect(userSync[0].type).to.equal('iframe'); + const USER_SYNC_URL = 'https://sync.logly.co.jp/sync/sync.html'; + expect(userSync[0].url).to.equal(USER_SYNC_URL); + }); + + it('When iframeEnabled is false, no userSync should be returned', function () { + const syncOptions = { + 'iframeEnabled': false + } + let userSync = spec.getUserSyncs(syncOptions, [nativeServerResponse]); + expect(userSync).to.be.an('array').that.is.empty; + }); + + it('When nativeServerResponses empty, no userSync should be returned', function () { + const syncOptions = { + 'iframeEnabled': false + } + let userSync = spec.getUserSyncs(syncOptions, []); + expect(userSync).to.be.an('array').that.is.empty; + }); + }); +}); diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index 5b55adffa9e..090144ab158 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -2,6 +2,7 @@ import { lotamePanoramaIdSubmodule, storage, } from 'modules/lotamePanoramaIdSystem.js'; +import { uspDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; import sinon from 'sinon'; @@ -16,6 +17,7 @@ describe('LotameId', function() { let setLocalStorageStub; let removeFromLocalStorageStub; let timeStampStub; + let uspConsentDataStub; const nowTimestamp = new Date().getTime(); @@ -30,6 +32,7 @@ describe('LotameId', function() { 'removeDataFromLocalStorage' ); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); }); afterEach(function () { @@ -40,6 +43,7 @@ describe('LotameId', function() { setLocalStorageStub.restore(); removeFromLocalStorageStub.restore(); timeStampStub.restore(); + uspConsentDataStub.restore(); }); describe('caching initial data received from the remote server', function () { @@ -726,4 +730,227 @@ describe('LotameId', function() { ); }); }); + + describe('with a custom client id', function () { + describe('with a client expiry set', function () { + beforeEach(function () { + getCookieStub + .withArgs('panoramaId_expiry_1234') + .returns(String(Date.now() + 500 * 1000)); + }); + + describe('and an existing pano id', function() { + let submoduleCallback; + beforeEach(function () { + getCookieStub + .withArgs('panoramaId_expiry') + .returns(String(Date.now() + 100000)); + getCookieStub + .withArgs('panoramaId') + .returns( + 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87c' + ); + submoduleCallback = lotamePanoramaIdSubmodule.getId( + { + params: { + clientId: '1234', + }, + }, + { + gdprApplies: false, + } + ); + }); + + it('should not call the remote server when getId is called nor get an id', function () { + expect(submoduleCallback).to.be.eql({ + id: undefined, + reason: 'NO_CLIENT_CONSENT', + }); + }); + }); + + describe('and no existing pano id', function () { + let submoduleCallback; + + beforeEach(function () { + // Let the panoramaId_expiry be empty + + submoduleCallback = lotamePanoramaIdSubmodule.getId( + { + params: { + clientId: '1234', + }, + }, + { + gdprApplies: false, + } + ); + }); + + it('should not call the remote server nor return an id', function () { + expect(submoduleCallback).to.be.eql({ + id: undefined, + reason: 'NO_CLIENT_CONSENT', + }); + }); + }); + }); + + describe('with no client expiry set', function () { + describe('and no existing pano id', function () { + let request; + let callBackSpy = sinon.spy(); + + beforeEach(function () { + uspConsentDataStub.returns('1NNN'); + let submoduleCallback = lotamePanoramaIdSubmodule.getId( + { + params: { + clientId: '1234', + }, + }, + { + gdprApplies: false, + } + ).callback; + submoduleCallback(callBackSpy); + + request = server.requests[0]; + + request.respond( + 200, + responseHeader, + JSON.stringify({ + profile_id: '4ec137245858469eb94a4e248f238694', + core_id: + 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87f', + expiry_ts: 3600000, + }) + ); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should pass the usp consent string and client id back', function () { + expect(request.url).to.be.eq( + 'https://id.crwdcntrl.net/id?gdpr_applies=false&us_privacy=1NNN&c=1234' + ); + }); + + it('should NOT set an expiry for the client', function () { + sinon.assert.neverCalledWith( + setCookieStub, + 'panoramaId_expiry_1234', + sinon.match.number + ); + }); + }); + + describe('and an existing pano id', function () { + let submoduleCallback; + + beforeEach(function () { + getCookieStub + .withArgs('panoramaId_expiry') + .returns(String(Date.now() + 100000)); + getCookieStub + .withArgs('panoramaId') + .returns( + 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87c' + ); + submoduleCallback = lotamePanoramaIdSubmodule.getId( + { + params: { + clientId: '1234', + }, + }, + { + gdprApplies: false, + } + ); + }); + + it('should not call the remote server but use the cached value', function () { + expect(submoduleCallback).to.be.eql({ + id: 'ca22992567e3cd4d116a5899b88a55d0d857a23610db939ae6ac13ba2335d87c', + }); + }); + + it('should NOT set an expiry for the client', function () { + sinon.assert.neverCalledWith( + setCookieStub, + 'panoramaId_expiry_1234', + sinon.match.number + ); + }); + }); + }); + describe('when client consent has errors', function () { + let request; + let callBackSpy = sinon.spy(); + + beforeEach(function () { + let submoduleCallback = lotamePanoramaIdSubmodule.getId( + { + params: { + clientId: '1234', + }, + }, + { + gdprApplies: false, + } + ).callback; + submoduleCallback(callBackSpy); + + request = server.requests[0]; + + request.respond( + 200, + responseHeader, + JSON.stringify({ + expiry_ts: 3600000, + errors: [111], + no_consent: 'CLIENT', + }) + ); + }); + + it('should call the remote server when getId is called', function () { + expect(callBackSpy.calledOnce).to.be.true; + }); + + it('should pass client id back', function () { + expect(request.url).to.be.eq( + 'https://id.crwdcntrl.net/id?gdpr_applies=false&c=1234' + ); + }); + + it('should set the received expiry for the client', function() { + sinon.assert.calledWith( + setCookieStub, + 'panoramaId_expiry_1234', + 3600000 + ); + }); + + it('should not clear the cache for the panorama id', function() { + sinon.assert.neverCalledWith( + setCookieStub, + 'panoramaId', + sinon.match.any + ); + }); + + it('should not clear the cache for the panorama id expiry', function () { + sinon.assert.neverCalledWith( + setCookieStub, + 'panoramaId_expiry', + sinon.match.any + ); + }); + }); + }); }); diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js new file mode 100755 index 00000000000..8aeecc87c98 --- /dev/null +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -0,0 +1,412 @@ +import { resetUserSync, spec, hasValidSupplyChainParams } from 'modules/luponmediaBidAdapter.js'; +const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; + +describe('luponmediaBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 12345, + 'keyId': '4o2c4' + }, + 'adUnitCode': 'test-div', + 'sizes': [[300, 250]], + 'bidId': 'g1987234bjkads', + 'bidderRequestId': '290348ksdhkas89324', + 'auctionId': '20384rlek235', + }; + + it('should return true when required params are found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'siteId': 12345 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 303522, + 'keyId': '4o2c4' + }, + 'crumbs': { + 'pubcid': '8d8b16cb-1383-4a0f-b4bb-0be28464d974' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1533155193780-2', + 'transactionId': '585d96a5-bd93-4a89-b8ea-0f546f3aaa82', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '268a30af10dd6f', + 'bidderRequestId': '140411b5010a2a', + 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + } + } + ]; + + let bidderRequest = { + 'bidderCode': 'luponmedia', + 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', + 'bidderRequestId': '140411b5010a2a', + 'bids': [ + { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 303522, + 'keyId': '4o2c4' + }, + 'crumbs': { + 'pubcid': '8d8b16cb-1383-4a0f-b4bb-0be28464d974' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1533155193780-2', + 'transactionId': '585d96a5-bd93-4a89-b8ea-0f546f3aaa82', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '268a30af10dd6f', + 'bidderRequestId': '140411b5010a2a', + 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + } + } + ], + 'auctionStart': 1587413920820, + 'timeout': 2000, + 'refererInfo': { + 'referer': 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines' + ] + }, + 'start': 1587413920835 + }; + + it('sends bid request to ENDPOINT via POST', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + let dynRes = JSON.parse(requests.data); + expect(requests.url).to.equal(ENDPOINT_URL); + expect(requests.method).to.equal('POST'); + expect(requests.data).to.equal('{"id":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","test":0,"source":{"tid":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"novi.ba","sid":"199424","hp":1}]}}},"tmax":1500,"imp":[{"id":"268a30af10dd6f","secure":1,"ext":{"luponmedia":{"siteId":303522,"keyId":"4o2c4"}},"banner":{"format":[{"w":300,"h":250}]}}],"ext":{"prebid":{"targeting":{"includewinners":true,"includebidderkeys":false}}},"user":{"id":"' + dynRes.user.id + '","buyeruid":"8d8b16cb-1383-4a0f-b4bb-0be28464d974"},"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}'); + }); + }); + + describe('interpretResponse', function () { + it('should get correct banner bid response', function () { + let response = { + 'id': '4776d680-15a2-45c3-bad5-db6bebd94a06', + 'seatbid': [ + { + 'bid': [ + { + 'id': '2a122246ef72ea', + 'impid': '2a122246ef72ea', + 'price': 0.43, + 'adm': ' ', + 'adid': '56380110', + 'cid': '44724710', + 'crid': '443801010', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'targeting': { + 'hb_bidder': 'luponmedia', + 'hb_pb': '0.40', + 'hb_size': '300x250' + }, + 'type': 'banner' + } + } + } + ], + 'seat': 'luponmedia' + } + ], + 'cur': 'USD', + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'ok', + 'bidder_status': [] + } + } + }; + + let expectedResponse = [ + { + 'requestId': '2a122246ef72ea', + 'cpm': '0.43', + 'width': 300, + 'height': 250, + 'creativeId': '443801010', + 'currency': 'USD', + 'dealId': '23425', + 'netRevenue': false, + 'ttl': 300, + 'referrer': '', + 'ad': ' ' + } + ]; + + let bidderRequest = { + 'data': '{"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}' + }; + + let result = spec.interpretResponse({ body: response }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function () { + let noBidResponse = []; + + let noBidBidderRequest = { + 'data': '{"site":{"page":""}}' + } + let noBidResult = spec.interpretResponse({ body: noBidResponse }, noBidBidderRequest); + expect(noBidResult.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + const bidResponse1 = { + 'body': { + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'ok', + 'bidder_status': [ + { + 'bidder': 'luponmedia', + 'no_cookie': true, + 'usersync': { + 'url': 'https://adxpremium.services/api/usersync', + 'type': 'redirect' + } + }, + { + 'bidder': 'luponmedia', + 'no_cookie': true, + 'usersync': { + 'url': 'https://adxpremium.services/api/iframeusersync', + 'type': 'iframe' + } + } + ] + } + } + } + }; + + const bidResponse2 = { + 'body': { + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'no_cookie', + 'bidder_status': [] + } + } + } + }; + + it('should use a sync url from first response (pixel and iframe)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [bidResponse1, bidResponse2]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://adxpremium.services/api/usersync' + }, + { + type: 'iframe', + url: 'https://adxpremium.services/api/iframeusersync' + } + ]); + }); + + it('handle empty response (e.g. timeout)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('returns empty syncs when not pixel enabled and not iframe enabled', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: false }, [bidResponse1]); + expect(syncs).to.deep.equal([]); + }); + + it('returns pixel syncs when pixel enabled and not iframe enabled', function() { + resetUserSync(); + + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: false }, [bidResponse1]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://adxpremium.services/api/usersync' + } + ]); + }); + + it('returns iframe syncs when not pixel enabled and iframe enabled', function() { + resetUserSync(); + + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: true }, [bidResponse1]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://adxpremium.services/api/iframeusersync' + } + ]); + }); + }); + + describe('hasValidSupplyChainParams', function () { + it('returns true if schain is valid', function () { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + }; + + const checkSchain = hasValidSupplyChainParams(schain); + expect(checkSchain).to.equal(true); + }); + + it('returns false if schain is invalid', function () { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'invalid': 'novi.ba' + } + ] + }; + + const checkSchain = hasValidSupplyChainParams(schain); + expect(checkSchain).to.equal(false); + }); + }); + + describe('onBidWon', function () { + const bidWonEvent = { + 'bidderCode': 'luponmedia', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '105bbf8c54453ff', + 'requestId': '934b8752185955', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.364, + 'creativeId': '443801010', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 300, + 'referrer': '', + 'ad': '', + 'auctionId': '926a8ea3-3dd4-4bf2-95ab-c85c2ce7e99b', + 'responseTimestamp': 1598527728026, + 'requestTimestamp': 1598527727629, + 'bidder': 'luponmedia', + 'adUnitCode': 'div-gpt-ad-1533155193780-5', + 'timeToRespond': 397, + 'size': '300x250', + 'status': 'rendered' + }; + + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'sendWinningsToServer') + }) + + afterEach(() => { + ajaxStub.restore() + }) + + it('calls luponmedia\'s callback endpoint', () => { + const result = spec.onBidWon(bidWonEvent); + expect(result).to.equal(undefined); + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.deep.equal(JSON.stringify(bidWonEvent)); + }); + }); +}); diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index f3f09a8ddf8..862b74f4f97 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -167,7 +167,7 @@ describe('MediaSquare bid adapter tests', function () { }); it('Verifies user sync without cookie in bid response', function () { var syncs = spec.getUserSyncs({}, [BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); }); it('Verifies user sync with cookies in bid response', function () { BID_RESPONSE.body.cookies = [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}]; @@ -178,13 +178,13 @@ describe('MediaSquare bid adapter tests', function () { }); it('Verifies user sync with no bid response', function() { var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); }); it('Verifies user sync with no bid body response', function() { var syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); var syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); - expect(syncs).to.have.property('type').and.to.equal('iframe'); + expect(syncs).to.have.lengthOf(0); }); it('Verifies native in bid response', function () { const request = spec.buildRequests(NATIVE_PARAMS, DEFAULT_OPTIONS); diff --git a/test/spec/modules/merkleIdSystem_spec.js b/test/spec/modules/merkleIdSystem_spec.js index 64dfc0d463c..63a2791ba3c 100644 --- a/test/spec/modules/merkleIdSystem_spec.js +++ b/test/spec/modules/merkleIdSystem_spec.js @@ -192,6 +192,24 @@ describe('Merkle System', function () { expect(id.id).to.exist.and.to.equal(storedId); }); + it('extendId() should warn on missing endpoint', function () { + let config = { + params: { + ...CONFIG_PARAMS, + endpoint: undefined + }, + storage: STORAGE_PARAMS + }; + + let yesterday = new Date(Date.now() - 86400000).toUTCString(); + let storedId = {value: 'Merkle_Stored_ID', date: yesterday}; + + let submoduleCallback = merkleIdSubmodule.extendId(config, undefined, + storedId).callback; + submoduleCallback(callbackSpy); + expect(callbackSpy.calledOnce).to.be.true; + expect(utils.logWarn.args[0][0]).to.exist.and.to.equal('User ID - merkleId submodule endpoint string is not defined'); + }); it('extendId() callback on configured storageParam.refreshInSeconds', function () { let config = { diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js new file mode 100644 index 00000000000..86b967cca5b --- /dev/null +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -0,0 +1,134 @@ +import { expect } from 'chai'; +import { spec, _getPlatform } from 'modules/missenaBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('Missena Adapter', function () { + const adapter = newBidder(spec); + + const bidId = 'abc'; + + const bid = { + bidder: 'missena', + bidId: bidId, + sizes: [[1, 1]], + params: { + apiKey: 'PA-34745704', + }, + }; + + describe('codes', function () { + it('should return a bidder code of missena', function () { + expect(spec.code).to.equal('missena'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true if the apiKey param is present', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if the apiKey is missing', function () { + expect( + spec.isBidRequestValid(Object.assign(bid, { params: {} })) + ).to.equal(false); + }); + + it('should return false if the apiKey is an empty string', function () { + expect( + spec.isBidRequestValid(Object.assign(bid, { params: { apiKey: '' } })) + ).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const consentString = 'AAAAAAAAA=='; + + const bidderRequest = { + gdprConsent: { + consentString: consentString, + gdprApplies: true, + }, + refererInfo: { + referer: 'https://referer', + canonicalUrl: 'https://canonical', + }, + }; + + const requests = spec.buildRequests([bid, bid], bidderRequest); + const request = requests[0]; + const payload = JSON.parse(request.data); + + it('should return as many server requests as bidder requests', function () { + expect(requests.length).to.equal(2); + }); + + it('should have a post method', function () { + expect(request.method).to.equal('POST'); + }); + + it('should send the bidder id', function () { + expect(payload.request_id).to.equal(bidId); + }); + + it('should send referer information to the request', function () { + expect(payload.referer).to.equal('https://referer'); + expect(payload.referer_canonical).to.equal('https://canonical'); + }); + + it('should send gdpr consent information to the request', function () { + expect(payload.consent_string).to.equal(consentString); + expect(payload.consent_required).to.equal(true); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + requestId: bidId, + cpm: 0.5, + currency: 'USD', + ad: '', + meta: { + advertiserDomains: ['missena.com'] + }, + }; + + const serverTimeoutResponse = { + requestId: bidId, + timeout: true, + ad: '', + }; + + const serverEmptyAdResponse = { + requestId: bidId, + cpm: 0.5, + currency: 'USD', + ad: '', + }; + + it('should return a proper bid response', function () { + const result = spec.interpretResponse({ body: serverResponse }, bid); + + expect(result.length).to.equal(1); + + expect(Object.keys(result[0])).to.have.members( + Object.keys(serverResponse) + ); + }); + + it('should return an empty response when the server answers with a timeout', function () { + const result = spec.interpretResponse( + { body: serverTimeoutResponse }, + bid + ); + expect(result).to.deep.equal([]); + }); + + it('should return an empty response when the server answers with an empty ad', function () { + const result = spec.interpretResponse( + { body: serverEmptyAdResponse }, + bid + ); + expect(result).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js index dfc9f5b99b3..23f48f3661a 100644 --- a/test/spec/modules/nativoBidAdapter_spec.js +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -8,29 +8,46 @@ import { spec } from 'modules/nativoBidAdapter.js' describe('nativoBidAdapterTests', function () { describe('isBidRequestValid', function () { let bid = { - bidder: 'nativo', - params: { - placementId: '10433394', - }, - adUnitCode: 'adunit-code', - sizes: [ - [300, 250], - [300, 600], - ], - bidId: '27b02036ccfa6e', - bidderRequestId: '1372cd8bd8d6a8', - auctionId: 'cfc467e4-2707-48da-becb-bcaab0b2c114', + bidder: 'nativo' } - it('should return true when required params found', function () { + it('should return true if no params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true for valid placementId value', function () { + bid.params = { + placementId: '10433394', + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return true for valid placementId value', function () { + bid.params = { + placementId: 10433394, + } + expect(spec.isBidRequestValid(bid)).to.equal(true) + }) + + it('should return false for invalid placementId value', function () { + bid.params = { + placementId: true, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + + it('should return true for valid placementId value', function () { + bid.params = { + url: 'www.test.com', + } expect(spec.isBidRequestValid(bid)).to.equal(true) }) - it('should return true when params are not passed', function () { - let bid2 = Object.assign({}, bid) - delete bid2.params - bid2.params = {} - expect(spec.isBidRequestValid(bid2)).to.equal(true) + it('should return false for invalid placementId value', function () { + bid.params = { + url: 4567890, + } + expect(spec.isBidRequestValid(bid)).to.equal(false) }) }) diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 1a24c6d0575..0dfee96d0ea 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -4,6 +4,7 @@ import { spec } from 'modules/nextMillenniumBidAdapter.js'; describe('nextMillenniumBidAdapterTests', function() { const bidRequestData = [ { + adUnitCode: 'test-div', bidId: 'bid1234', auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidder: 'nextMillennium', @@ -17,19 +18,43 @@ describe('nextMillenniumBidAdapterTests', function() { } ]; - it('Request params check with GDPR Consent', function () { + it('Request params check with GDPR and USP Consent', function () { const request = spec.buildRequests(bidRequestData, bidRequestData[0]); expect(JSON.parse(request[0].data).user.ext.consent).to.equal('kjfdniwjnifwenrif3'); expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('1---'); expect(JSON.parse(request[0].data).regs.ext.gdpr).to.equal(1); }); + it('Request params check without GDPR Consent', function () { + delete bidRequestData[0].gdprConsent + const request = spec.buildRequests(bidRequestData, bidRequestData[0]); + expect(JSON.parse(request[0].data).regs.ext.gdpr).to.be.undefined; + expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('1---'); + }); + it('validate_generated_params', function() { const request = spec.buildRequests(bidRequestData); expect(request[0].bidId).to.equal('bid1234'); expect(JSON.parse(request[0].data).id).to.equal('b06c5141-fe8f-4cdf-9d7d-54415490a917'); }); + it('Check if refresh_count param is incremented', function() { + const request = spec.buildRequests(bidRequestData); + expect(JSON.parse(request[0].data).ext.nextMillennium.refresh_count).to.equal(3); + }); + + it('Test getUserSyncs function', function () { + const syncOptions = { + 'iframeEnabled': true + } + const userSync = spec.getUserSyncs(syncOptions); + expect(userSync).to.be.an('array').with.lengthOf(1); + expect(userSync[0].type).to.exist; + expect(userSync[0].url).to.exist; + expect(userSync[0].type).to.be.equal('iframe'); + expect(userSync[0].url).to.be.equal('https://statics.nextmillmedia.com/load-cookie.html?v=4'); + }); + it('validate_response_params', function() { const serverResponse = { body: { diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index 883119d2707..225d0cd30ef 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -119,7 +119,7 @@ describe('OguryBidAdapter', function () { }; }); - it('should return sync array with two elements of type image', () => { + it('should return syncs array with two elements of type image', () => { const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); expect(userSyncs).to.have.lengthOf(2); @@ -129,7 +129,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.contain('https://ms-cookie-sync.presage.io/ttd/init-sync'); }); - it('should set the same source as query param', () => { + it('should set the source as query param', () => { const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); expect(userSyncs[0].url).to.contain('source=prebid'); expect(userSyncs[1].url).to.contain('source=prebid'); @@ -146,7 +146,7 @@ describe('OguryBidAdapter', function () { expect(spec.getUserSyncs(syncOptions, [], gdprConsent)).to.have.lengthOf(0); }); - it('should return sync array with two elements of type image when consentString is undefined', () => { + it('should return syncs array with two elements of type image when consentString is undefined', () => { gdprConsent = { gdprApplies: true, consentString: undefined @@ -160,7 +160,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') }); - it('should return sync array with two elements of type image when consentString is null', () => { + it('should return syncs array with two elements of type image when consentString is null', () => { gdprConsent = { gdprApplies: true, consentString: null @@ -174,7 +174,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') }); - it('should return sync array with two elements of type image when gdprConsent is undefined', () => { + it('should return syncs array with two elements of type image when gdprConsent is undefined', () => { gdprConsent = undefined; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); @@ -185,7 +185,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') }); - it('should return sync array with two elements of type image when gdprConsent is null', () => { + it('should return syncs array with two elements of type image when gdprConsent is null', () => { gdprConsent = null; const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); @@ -196,7 +196,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') }); - it('should return sync array with two elements of type image when gdprConsent is null and gdprApplies is false', () => { + it('should return syncs array with two elements of type image when gdprConsent is null and gdprApplies is false', () => { gdprConsent = { gdprApplies: false, consentString: null @@ -210,7 +210,7 @@ describe('OguryBidAdapter', function () { expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') }); - it('should return sync array with two elements of type image when gdprConsent is empty string and gdprApplies is false', () => { + it('should return syncs array with two elements of type image when gdprConsent is empty string and gdprApplies is false', () => { gdprConsent = { gdprApplies: false, consentString: '' @@ -240,7 +240,8 @@ describe('OguryBidAdapter', function () { w: 300, h: 250 }] - } + }, + ext: bidRequests[0].params }, { id: bidRequests[1].bidId, tagid: bidRequests[1].params.adUnitId, @@ -250,7 +251,8 @@ describe('OguryBidAdapter', function () { w: 600, h: 500 }] - } + }, + ext: bidRequests[1].params }], regs: { ext: { @@ -266,6 +268,10 @@ describe('OguryBidAdapter', function () { ext: { consent: bidderRequest.gdprConsent.consentString }, + }, + ext: { + prebidversion: '$prebid.version$', + adapterversion: '1.2.7' } }; @@ -423,7 +429,9 @@ describe('OguryBidAdapter', function () { meta: { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, - nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl + nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, + adapterVersion: '1.2.7', + prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, cpm: openRtbBidResponse.body.seatbid[0].bid[1].price, @@ -438,7 +446,9 @@ describe('OguryBidAdapter', function () { meta: { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, - nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl + nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, + adapterVersion: '1.2.7', + prebidVersion: '$prebid.version$' }] let request = spec.buildRequests(bidRequests, bidderRequest); @@ -549,7 +559,7 @@ describe('OguryBidAdapter', function () { xhr.restore() }) - it('should send notification on bid timeout', function() { + it('should send on bid timeout notification', function() { const bid = { ad: 'cookies', cpm: 3 diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 783449723ae..3bc53e30eb8 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1102,6 +1102,11 @@ describe('OpenxAdapter', function () { mwOpenLinkId: '1111-mwopenlinkid', dapId: '1111-dapId', amxId: '1111-amxid', + kpuid: '1111-kpuid', + publinkId: '1111-publinkid', + naveggId: '1111-naveggid', + imuid: '1111-imuid', + adtelligentId: '1111-adtelligentid' }; // generates the same set of tests for each id provider diff --git a/test/spec/modules/optimeraRtdProvider_spec.js b/test/spec/modules/optimeraRtdProvider_spec.js index 8b1866d044a..7883412ab70 100644 --- a/test/spec/modules/optimeraRtdProvider_spec.js +++ b/test/spec/modules/optimeraRtdProvider_spec.js @@ -35,17 +35,30 @@ describe('Optimera RTD score file properly sets targeting values', () => { 'de': { 'div-0': ['A5', 'A6'], 'div-1': ['A7', 'A8'], + 'insights': { + 'ilv': ['div-0'], + 'miv': ['div-4'], + } }, 'mo': { 'div-0': ['A9', 'B0'], 'div-1': ['B1', 'B2'], + 'insights': { + 'ilv': ['div-1'], + 'miv': ['div-2'], + } } + }, + 'insights': { + 'ilv': ['div-5'], + 'miv': ['div-6'], } }; - it('Properly set the score file url', () => { + it('Properly set the score file url and scores', () => { optimeraRTD.setScores(JSON.stringify(scores)); expect(optimeraRTD.optimeraTargeting['div-0']).to.include.ordered.members(['A5', 'A6']); expect(optimeraRTD.optimeraTargeting['div-1']).to.include.ordered.members(['A7', 'A8']); + expect(window.optimeraInsights.data['ilv']).to.include.ordered.members(['div-0']); }); }); diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index f9941b41189..658af310ea5 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -69,8 +69,6 @@ var validBidRequestsMulti = [ transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; -// use 'pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'criteoId' -// see http://prebid.org/dev-docs/modules/userId.html var validBidRequestsWithUserIdData = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -291,7 +289,6 @@ var validBidRequests1OutstreamVideo2020 = [ } ]; -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var validBidderRequest1OutstreamVideo2020 = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -394,7 +391,6 @@ var validBidderRequest1OutstreamVideo2020 = { timeout: 3000 } }; -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var validBidderRequest = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -419,11 +415,6 @@ var validBidderRequest = { } }; -// bidder request with GDPR - change the values for testing: -// gdprConsent.gdprApplies (true/false) -// gdprConsent.vendorData.purposeConsents (make empty, make null, remove it) -// gdprConsent.vendorData.vendorConsents (remove 524, remove all, make the element null, remove it) -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var bidderRequestWithFullGdpr = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -512,7 +503,6 @@ var gdpr1 = { 'gdprApplies': true }; -// simulating the Mirror var bidderRequestWithPartialGdpr = { bidderRequest: { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', @@ -558,7 +548,6 @@ var bidderRequestWithPartialGdpr = { } }; -// make sure the impid matches the request bidId var validResponse = { 'body': { 'id': 'd6198807-7a53-4141-b2db-d2cb754d68ba', @@ -1113,7 +1102,6 @@ var multiRequest1 = [ } ]; -// WHEN sent as bidderRequest to buildRequests you should send the child: .bidderRequest var multiBidderRequest1 = { bidderRequest: { 'bidderCode': 'ozone', @@ -1507,7 +1495,6 @@ var multiResponse1 = { describe('ozone Adapter', function () { describe('isBidRequestValid', function () { - // A test ad unit that will consistently return test creatives let validBidReq = { bidder: BIDDER_CODE, params: { @@ -1941,7 +1928,6 @@ describe('ozone Adapter', function () { const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest.bidderRequest); expect(request).to.be.a('array'); expect(request[0]).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - // need to reset the singleRequest config flag: config.setConfig({'ozone': {'singleRequest': true}}); }); @@ -1965,7 +1951,6 @@ describe('ozone Adapter', function () { expect(payload.user.ext.consent).to.equal(consentString); }); - // mirror it('should add gdpr consent information to the request when vendorData is missing vendorConsents (Mirror)', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; let bidderRequest = validBidderRequest.bidderRequest; @@ -2018,7 +2003,6 @@ describe('ozone Adapter', function () { }; let bidRequests = validBidRequests; - // values from http://prebid.org/dev-docs/modules/userId.html#pubcommon-id bidRequests[0]['userId'] = { 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, @@ -2038,13 +2022,11 @@ describe('ozone Adapter', function () { it('should pick up the value of pubcid when built using the pubCommonId module (not userId)', function () { let bidRequests = validBidRequests; - // values from http://prebid.org/dev-docs/modules/userId.html#pubcommon-id bidRequests[0]['userId'] = { 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, 'idl_env': '3333', 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', - // 'pubcid': '5555', // remove pubcid from here to emulate the OLD module & cause the failover code to kick in 'tdid': '6666', 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} }; @@ -2170,7 +2152,6 @@ describe('ozone Adapter', function () { }); it('should use oztestmode GET value if set', function() { var specMock = utils.deepClone(spec); - // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; @@ -2181,7 +2162,6 @@ describe('ozone Adapter', function () { }); it('should pass through GET params if present: ozf, ozpf, ozrp, ozip', function() { var specMock = utils.deepClone(spec); - // mock the getGetParametersAsObject function to simulate GET parameters : specMock.getGetParametersAsObject = function() { return {ozf: '1', ozpf: '0', ozrp: '2', ozip: '123'}; }; @@ -2194,7 +2174,6 @@ describe('ozone Adapter', function () { }); it('should pass through GET params if present: ozf, ozpf, ozrp, ozip with alternative values', function() { var specMock = utils.deepClone(spec); - // mock the getGetParametersAsObject function to simulate GET parameters : specMock.getGetParametersAsObject = function() { return {ozf: 'false', ozpf: 'true', ozrp: 'xyz', ozip: 'hello'}; }; @@ -2207,7 +2186,6 @@ describe('ozone Adapter', function () { }); it('should use oztestmode GET value if set, even if there is no customdata in config', function() { var specMock = utils.deepClone(spec); - // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: specMock.getGetParametersAsObject = function() { return {'oztestmode': 'mytestvalue_123'}; }; @@ -2217,7 +2195,6 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.ozone.customData[0].targeting.oztestmode).to.equal('mytestvalue_123'); }); it('should use GET values auction=dev & cookiesync=dev if set', function() { - // mock the getGetParametersAsObject function to simulate GET parameters for oztestmode: var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {}; @@ -2228,8 +2205,6 @@ describe('ozone Adapter', function () { let cookieUrl = specMock.getCookieSyncUrl(); expect(cookieUrl).to.equal('https://elb.the-ozone-project.com/static/load-cookie.html'); - // now mock the response from getGetParametersAsObject & do the request again - specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {'auction': 'dev', 'cookiesync': 'dev'}; @@ -2241,7 +2216,6 @@ describe('ozone Adapter', function () { expect(cookieUrl).to.equal('https://test.ozpr.net/static/load-cookie.html'); }); it('should use a valid ozstoredrequest GET value if set to override the placementId values, and set oz_rw if we find it', function() { - // mock the getGetParametersAsObject function to simulate GET parameters for ozstoredrequest: var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {'ozstoredrequest': '1122334455'}; // 10 digits are valid @@ -2252,7 +2226,6 @@ describe('ozone Adapter', function () { expect(data.imp[0].ext.prebid.storedrequest.id).to.equal('1122334455'); }); it('should NOT use an invalid ozstoredrequest GET value if set to override the placementId values, and set oz_rw to 0', function() { - // mock the getGetParametersAsObject function to simulate GET parameters for ozstoredrequest: var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { return {'ozstoredrequest': 'BADVAL'}; // 10 digits are valid @@ -2424,6 +2397,26 @@ describe('ozone Adapter', function () { expect(utils.deepAccess(payload, 'imp.0.floor.banner.floor')).to.equal(0.8); config.resetConfig(); }); + + it('handles schain object in each bidrequest (will be the same in each br)', function () { + let br = JSON.parse(JSON.stringify(validBidRequests)); + let schainConfigObject = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'bidderA.com', + 'sid': '00001', + 'hp': 1 + } + ] + }; + br[0]['schain'] = schainConfigObject; + const request = spec.buildRequests(br, validBidderRequest.bidderRequest); + const data = JSON.parse(request.data); + expect(data.source.ext).to.haveOwnProperty('schain'); + expect(data.source.ext.schain).to.deep.equal(schainConfigObject); // .deep.equal() : Target object deeply (but not strictly) equals `{a: 1}` + }); }); describe('interpretResponse', function () { @@ -2624,7 +2617,6 @@ describe('ozone Adapter', function () { expect(result[1]['impid']).to.equal('3025f169863b7f8'); expect(result[1]['id']).to.equal('18552976939844999'); expect(result[1]['adserverTargeting']['oz_ozappnexus_adId']).to.equal('3025f169863b7f8-0-oz-2'); - // change the bid values so a different second bid for an impid by the same bidder gets dropped validres = JSON.parse(JSON.stringify(multiResponse1)); validres.body.seatbid[0].bid[1].price = 1.1; validres.body.seatbid[0].bid[1].cpm = 1.1; @@ -2647,7 +2639,6 @@ describe('ozone Adapter', function () { expect(result).to.be.empty; }); it('should append the various values if they exist', function() { - // get the cookie bag populated spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1); expect(result).to.be.an('array'); @@ -2657,14 +2648,12 @@ describe('ozone Adapter', function () { expect(result[0].url).to.include('gdpr_consent=BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA'); }); it('should append ccpa (usp data)', function() { - // get the cookie bag populated spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1, '1YYN'); expect(result).to.be.an('array'); expect(result[0].url).to.include('usp_consent=1YYN'); }); it('should use "" if no usp is sent to cookieSync', function() { - // get the cookie bag populated spec.buildRequests(validBidRequests, validBidderRequest.bidderRequest); const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1); expect(result).to.be.an('array'); @@ -2887,4 +2876,27 @@ describe('ozone Adapter', function () { expect(response[1].bid.length).to.equal(2); }); }); + /** + * spec.getWhitelabelConfigItem test - get a config value for a whitelabelled bidder, + * from a standard ozone.oz_xxxx_yyy string + */ + describe('getWhitelabelConfigItem', function() { + it('should fetch the whitelabelled equivalent config value correctly', function () { + var specMock = utils.deepClone(spec); + config.setConfig({'ozone': {'oz_omp_floor': 'ozone-floor-value'}}); + config.setConfig({'markbidder': {'mb_omp_floor': 'markbidder-floor-value'}}); + specMock.propertyBag.whitelabel = {bidder: 'ozone', keyPrefix: 'oz'}; + let testKey = 'ozone.oz_omp_floor'; + let ozone_value = specMock.getWhitelabelConfigItem(testKey); + expect(ozone_value).to.equal('ozone-floor-value'); + specMock.propertyBag.whitelabel = {bidder: 'markbidder', keyPrefix: 'mb'}; + let markbidder_config = specMock.getWhitelabelConfigItem(testKey); + expect(markbidder_config).to.equal('markbidder-floor-value'); + config.setConfig({'markbidder': {'singleRequest': 'markbidder-singlerequest-value'}}); + let testKey2 = 'ozone.singleRequest'; + let markbidder_config2 = specMock.getWhitelabelConfigItem(testKey2); + expect(markbidder_config2).to.equal('markbidder-singlerequest-value'); + config.resetConfig(); + }); + }); }); diff --git a/test/spec/modules/pilotxBidAdapter_spec.js b/test/spec/modules/pilotxBidAdapter_spec.js new file mode 100644 index 00000000000..2ef31c0a8f5 --- /dev/null +++ b/test/spec/modules/pilotxBidAdapter_spec.js @@ -0,0 +1,244 @@ +// import or require modules necessary for the test, e.g.: +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { spec } from '../../../modules/pilotxBidAdapter.js'; + +describe('pilotxAdapter', function () { + describe('isBidRequestValid', function () { + let banner; + beforeEach(function () { + banner = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + sizes: [[300, 250], [468, 60]], + bidId: '2de8c82e30665a', + params: { + placementId: '1' + } + }; + }); + + it('should return false if sizes is empty', function () { + banner.sizes = [] + expect(spec.isBidRequestValid(banner)).to.equal(false); + }); + it('should return true if all is valid/ is not empty', function () { + expect(spec.isBidRequestValid(banner)).to.equal(true); + }); + it('should return false if there is no placement id found', function () { + banner.params = {} + expect(spec.isBidRequestValid(banner)).to.equal(false); + }); + it('should return false if sizes is empty', function () { + banner.sizes = [] + expect(spec.isBidRequestValid(banner)).to.equal(false); + }); + it('should return false for no size and empty params', function() { + const emptySizes = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + bidId: '2de8c82e30665a', + params: { + placementId: '1', + sizes: [] + } + }; + expect(spec.isBidRequestValid(emptySizes)).to.equal(false); + }) + it('should return true for no size and valid size params', function() { + const emptySizes = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + bidId: '2de8c82e30665a', + params: { + placementId: '1', + sizes: [[300, 250], [468, 60]] + } + }; + expect(spec.isBidRequestValid(emptySizes)).to.equal(true); + }) + it('should return false for no size items', function() { + const emptySizes = { + bidder: 'pilotx', + adUnitCode: 'adunit-test', + mediaTypes: { banner: {} }, + bidId: '2de8c82e30665a', + params: { + placementId: '1' + } + }; + expect(spec.isBidRequestValid(emptySizes)).to.equal(false); + }) + }); + + describe('buildRequests', function () { + const mockRequest = { refererInfo: {} }; + const mockRequestGDPR = { + refererInfo: {}, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + gdprApplies: true + } + + } + const mockVideo1 = [{ + adUnitCode: 'video1', + auctionId: '01618029-7ae9-4e98-a73a-1ed0c817f414', + bidId: '2a59588c0114fa', + bidRequestsCount: 1, + bidder: 'pilotx', + bidderRequestId: '1f6b4ba2039726', + bidderRequestsCount: 1, + bidderWinsCount: 0, + crumbs: { pubcid: 'de5240ef-ff80-4b55-8837-26a11cfbf64c' }, + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mp4'], + playbackmethod: [2], + playerSize: [[640, 480]], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1 + } + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'video1' + } + } + }, + params: { placementId: '379' }, + sizes: [[640, 480]], + src: 'client', + transactionId: 'fec9f2ff-da13-4921-8437-8d679c2be7fe', + }]; + const mockVideo2 = [{ + adUnitCode: 'video1', + auctionId: '01618029-7ae9-4e98-a73a-1ed0c817f414', + bidId: '2a59588c0114fa', + bidRequestsCount: 1, + bidder: 'pilotx', + bidderRequestId: '1f6b4ba2039726', + bidderRequestsCount: 1, + bidderWinsCount: 0, + crumbs: { pubcid: 'de5240ef-ff80-4b55-8837-26a11cfbf64c' }, + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mp4'], + playbackmethod: [2], + playerSize: [[640, 480]], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1 + } + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'video1' + } + } + }, + params: { placementId: '379' }, + sizes: [640, 480], + src: 'client', + transactionId: 'fec9f2ff-da13-4921-8437-8d679c2be7fe', + }]; + it('should return correct response', function () { + const builtRequest = spec.buildRequests(mockVideo1, mockRequest) + let builtRequestData = builtRequest.data + let data = JSON.parse(builtRequestData) + expect(data['379'].bidId).to.equal(mockVideo1[0].bidId) + }); + it('should return correct response for only array of size', function () { + const builtRequest = spec.buildRequests(mockVideo2, mockRequest) + let builtRequestData = builtRequest.data + let data = JSON.parse(builtRequestData) + expect(data['379'].sizes[0][0]).to.equal(mockVideo2[0].sizes[0]) + expect(data['379'].sizes[0][1]).to.equal(mockVideo2[0].sizes[1]) + }); + it('should be valid and pass gdpr items correctly', function () { + const builtRequest = spec.buildRequests(mockVideo2, mockRequestGDPR) + let builtRequestData = builtRequest.data + let data = JSON.parse(builtRequestData) + expect(data['379'].gdprConsentString).to.equal(mockRequestGDPR.gdprConsent.consentString) + expect(data['379'].gdprConsentRequired).to.equal(mockRequestGDPR.gdprConsent.gdprApplies) + }); + }); + describe('interpretResponse', function () { + const bidRequest = {} + const serverResponse = { + cpm: 2.5, + creativeId: 'V9060', + currency: 'US', + height: 480, + mediaType: 'video', + netRevenue: false, + requestId: '273b39c74069cb', + ttl: 3000, + vastUrl: 'http://testadserver.com/ads?&k=60cd901ad8ab70c9cedf373cb17b93b8&pid=379&tid=91342717', + width: 640 + } + const serverResponseVideo = { + body: serverResponse + } + const serverResponse2 = { + cpm: 2.5, + creativeId: 'V9060', + currency: 'US', + height: 480, + mediaType: 'banner', + netRevenue: false, + requestId: '273b39c74069cb', + ttl: 3000, + vastUrl: 'http://testadserver.com/ads?&k=60cd901ad8ab70c9cedf373cb17b93b8&pid=379&tid=91342717', + width: 640 + } + const serverResponseBanner = { + body: serverResponse2 + } + it('should be valid from bidRequest for video', function () { + const bidResponses = spec.interpretResponse(serverResponseVideo, bidRequest) + expect(bidResponses[0].requestId).to.equal(serverResponse.requestId) + expect(bidResponses[0].cpm).to.equal(serverResponse.cpm) + expect(bidResponses[0].width).to.equal(serverResponse.width) + expect(bidResponses[0].height).to.equal(serverResponse.height) + expect(bidResponses[0].creativeId).to.equal(serverResponse.creativeId) + expect(bidResponses[0].currency).to.equal(serverResponse.currency) + expect(bidResponses[0].netRevenue).to.equal(serverResponse.netRevenue) + expect(bidResponses[0].ttl).to.equal(serverResponse.ttl) + expect(bidResponses[0].vastUrl).to.equal(serverResponse.vastUrl) + expect(bidResponses[0].mediaType).to.equal(serverResponse.mediaType) + expect(bidResponses[0].meta.mediaType).to.equal(serverResponse.mediaType) + }); + it('should be valid from bidRequest for banner', function () { + const bidResponses = spec.interpretResponse(serverResponseBanner, bidRequest) + expect(bidResponses[0].requestId).to.equal(serverResponse2.requestId) + expect(bidResponses[0].cpm).to.equal(serverResponse2.cpm) + expect(bidResponses[0].width).to.equal(serverResponse2.width) + expect(bidResponses[0].height).to.equal(serverResponse2.height) + expect(bidResponses[0].creativeId).to.equal(serverResponse2.creativeId) + expect(bidResponses[0].currency).to.equal(serverResponse2.currency) + expect(bidResponses[0].netRevenue).to.equal(serverResponse2.netRevenue) + expect(bidResponses[0].ttl).to.equal(serverResponse2.ttl) + expect(bidResponses[0].ad).to.equal(serverResponse2.ad) + expect(bidResponses[0].mediaType).to.equal(serverResponse2.mediaType) + expect(bidResponses[0].meta.mediaType).to.equal(serverResponse2.mediaType) + }); + }); + describe('setPlacementID', function () { + const multiplePlacementIds = ['380', '381'] + it('should be valid with an array of placement ids passed', function () { + const placementID = spec.setPlacementID(multiplePlacementIds) + expect(placementID).to.equal('380#381') + }); + it('should be valid with single placement ID passed', function () { + const placementID = spec.setPlacementID('381') + expect(placementID).to.equal('381') + }); + }); + // Add other `describe` or `it` blocks as necessary +}); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index a06d4dbcd68..73a531b30bf 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -8,6 +8,7 @@ import events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import { server } from 'test/mocks/xhr.js'; import { createEidsArray } from 'modules/userId/eids.js'; +import {deepAccess, deepClone} from 'src/utils.js'; let CONFIG = { accountId: '1', @@ -507,6 +508,17 @@ describe('S2S Adapter', function () { resetSyncedStatus(); }); + it('should set id to auction ID and source.tid to tid', function () { + config.setConfig({ s2sConfig: CONFIG }); + + adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + + const requestBid = JSON.parse(server.requests[0].requestBody); + expect(requestBid.id).to.equal('173afb6d132ba3'); + expect(requestBid.source).to.be.an('object'); + expect(requestBid.source.tid).to.equal('437fbbf5-33f5-487a-8e16-a7112903cfe5'); + }); + it('should block request if config did not define p1Consent URL in endpoint object config', function() { let badConfig = utils.deepClone(CONFIG); badConfig.endpoint = { noP1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' }; @@ -1081,6 +1093,18 @@ describe('S2S Adapter', function () { }); }); + it('should not include ext.aspectratios if adunit\'s aspect_ratios do not define radio_width and ratio_height', () => { + const req = deepClone(REQUEST); + req.ad_units[0].mediaTypes.native.icon.aspect_ratios[0] = {'min_width': 1, 'min_height': 2}; + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const nativeReq = JSON.parse(JSON.parse(server.requests[0].requestBody).imp[0].native.request); + const icons = nativeReq.assets.map((a) => a.img).filter((img) => img && img.type === 1); + expect(icons).to.have.length(1); + expect(icons[0].hmin).to.equal(2); + expect(icons[0].wmin).to.equal(1); + expect(deepAccess(icons[0], 'ext.aspectratios')).to.be.undefined; + }) + it('adds site if app is not present', function () { const _config = { s2sConfig: CONFIG, @@ -1705,7 +1729,7 @@ describe('S2S Adapter', function () { expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({name: 'pbjs', version: 'v$prebid.version$'}); }); - it('does not set pbjs version in request if channel does exist in s2sConfig', () => { + it('extPrebid is now mergedDeep -> should include default channel as well', () => { const s2sBidRequest = utils.deepClone(REQUEST); const bidRequests = utils.deepClone(BID_REQUESTS); @@ -1714,7 +1738,13 @@ describe('S2S Adapter', function () { adapter.callBids(s2sBidRequest, bidRequests, addBidResponse, done, ajax); const parsedRequestBody = JSON.parse(server.requests[0].requestBody); - expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({test: 1}); + + // extPrebid is now deep merged with + expect(parsedRequestBody.ext.prebid.channel).to.deep.equal({ + name: 'pbjs', + test: 1, + version: 'v$prebid.version$' + }); }); it('passes first party data in request', () => { @@ -2403,6 +2433,36 @@ describe('S2S Adapter', function () { utils.getBidRequest.restore(); }); + + describe('on sync requested with no cookie', () => { + let cfg, req, csRes; + + beforeEach(() => { + cfg = utils.deepClone(CONFIG); + req = utils.deepClone(REQUEST); + cfg.syncEndpoint = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; + req.s2sConfig = cfg; + config.setConfig({ s2sConfig: cfg }); + csRes = utils.deepClone(RESPONSE_NO_COOKIE); + }); + + afterEach(() => { + resetSyncedStatus(); + }) + + Object.entries({ + iframe: () => utils.insertUserSyncIframe, + image: () => utils.triggerPixel, + }).forEach(([type, syncer]) => { + it(`passes timeout to ${type} syncs`, () => { + cfg.syncTimeout = 123; + csRes.bidder_status[0].usersync.type = type; + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(csRes)); + expect(syncer().args[0]).to.include.members([123]); + }); + }); + }); }); describe('bid won events', function () { diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 6568f7aa782..4599eb2a6fa 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -173,7 +173,7 @@ describe('pubGENIUS adapter', () => { expectedRequest = { method: 'POST', - url: 'https://ortb.adpearl.io/prebid/auction', + url: 'https://auction.adpearl.io/prebid/auction', data: { id: 'fake-auction-id', imp: [ @@ -493,7 +493,7 @@ describe('pubGENIUS adapter', () => { }; expectedSync = { type: 'iframe', - url: 'https://ortb.adpearl.io/usersync/pixels.html?', + url: 'https://auction.adpearl.io/usersync/pixels.html?', }; }); @@ -551,7 +551,7 @@ describe('pubGENIUS adapter', () => { onTimeout(timeoutData); expect(server.requests[0].method).to.equal('POST'); - expect(server.requests[0].url).to.equal('https://ortb.adpearl.io/prebid/events?type=timeout'); + expect(server.requests[0].url).to.equal('https://auction.adpearl.io/prebid/events?type=timeout'); expect(JSON.parse(server.requests[0].requestBody)).to.deep.equal(timeoutData); }); }); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index c6496ee7fe1..b6268548ccc 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -95,6 +95,9 @@ const BID2 = Object.assign({}, BID, { 'hb_pb': '1.500', 'hb_size': '728x90', 'hb_source': 'server' + }, + meta: { + advertiserDomains: ['example.com'] } }); @@ -382,6 +385,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -651,6 +655,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -708,6 +713,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -754,6 +760,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -771,9 +778,10 @@ describe('pubmatic analytics adapter', function () { expect(data.kgpv).to.equal('*'); }); - it('Logger: regexPattern in bid.bidResponse', function() { + it('Logger: regexPattern in bid.bidResponse and url in adomain', function() { const BID2_COPY = utils.deepClone(BID2); BID2_COPY.regexPattern = '*'; + BID2_COPY.meta.advertiserDomains = ['https://www.example.com/abc/223'] events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); @@ -808,6 +816,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -859,6 +868,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -912,6 +922,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); @@ -1011,6 +1022,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].di).to.equal('the-deal-id'); expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); + expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[1].ps[0].l1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 5e59fdd2e59..9696501437b 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -3457,7 +3457,7 @@ describe('PubMatic adapter', function () { 'h': 0, 'dealId': 'ASEA-MS-KLY-TTD-DESKTOP-ID-VID-6S-030420', 'ext': { - 'BidType': 1 + 'bidtype': 1 } }], 'ext': { @@ -3861,44 +3861,4 @@ describe('PubMatic adapter', function () { }) }); }); - - describe('Video request params', function() { - let sandbox, utilsMock, newVideoRequest; - beforeEach(() => { - utilsMock = sinon.mock(utils); - sandbox = sinon.sandbox.create(); - sandbox.spy(utils, 'logWarn'); - newVideoRequest = utils.deepClone(videoBidRequests) - }); - - afterEach(() => { - utilsMock.restore(); - sandbox.restore(); - }) - - it('Should log warning if video params from mediaTypes and params obj of bid are not present', function () { - delete newVideoRequest[0].mediaTypes.video; - delete newVideoRequest[0].params.video; - - let request = spec.buildRequests(newVideoRequest, { - auctionId: 'new-auction-id' - }); - - sinon.assert.calledOnce(utils.logWarn); - expect(request).to.equal(undefined); - }); - - it('Should consider video params from mediaType object of bid', function () { - delete newVideoRequest[0].params.video; - - let request = spec.buildRequests(newVideoRequest, { - auctionId: 'new-auction-id' - }); - let data = JSON.parse(request.data); - expect(data.imp[0].video).to.exist; - expect(data.imp[0]['video']['w']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[0]); - expect(data.imp[0]['video']['h']).to.equal(videoBidRequests[0].mediaTypes.video.playerSize[1]); - expect(data.imp[0]['video']['battr']).to.equal(undefined); - }); - }); }); diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 3d9be082be3..e6a1c063a86 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -28,6 +28,7 @@ describe('pubxai analytics adapter', function() { }; let location = utils.getWindowLocation(); + let storage = window.top['sessionStorage']; let prebidEvent = { 'auctionInit': { @@ -514,6 +515,11 @@ describe('pubxai analytics adapter', function() { 'path': location.pathname, 'search': location.search }, + 'pmcDetail': { + 'bidDensity': storage.getItem('pbx:dpbid'), + 'maxBid': storage.getItem('pbx:mxbid'), + 'auctionId': storage.getItem('pbx:aucid') + } }; let expectedAfterBid = { @@ -577,6 +583,11 @@ describe('pubxai analytics adapter', function() { 'deviceOS': getOS(), 'browser': getBrowser() }, + 'pmcDetail': { + 'bidDensity': storage.getItem('pbx:dpbid'), + 'maxBid': storage.getItem('pbx:mxbid'), + 'auctionId': storage.getItem('pbx:aucid') + }, 'initOptions': initOptions }; diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js index b84aef15feb..11639733661 100644 --- a/test/spec/modules/realTimeDataModule_spec.js +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -1,6 +1,9 @@ import * as rtdModule from 'modules/rtdModule/index.js'; -import { config } from 'src/config.js'; +import {config} from 'src/config.js'; import * as sinon from 'sinon'; +import {default as CONSTANTS} from '../../../src/constants.json'; +import {default as events} from '../../../src/events.js'; +import 'src/prebid.js'; const getBidRequestDataSpy = sinon.spy(); @@ -58,33 +61,136 @@ const conf = { }; describe('Real time module', function () { - before(function () { - rtdModule.attachRealTimeDataProvider(validSM); - rtdModule.attachRealTimeDataProvider(invalidSM); - rtdModule.attachRealTimeDataProvider(failureSM); - rtdModule.attachRealTimeDataProvider(nonConfSM); - rtdModule.attachRealTimeDataProvider(validSMWait); - }); + let eventHandlers; + let sandbox; - after(function () { - config.resetConfig(); - }); + function mockEmitEvent(event, ...args) { + (eventHandlers[event] || []).forEach((h) => h(...args)); + } - beforeEach(function () { - config.setConfig(conf); + before(() => { + eventHandlers = {}; + sandbox = sinon.sandbox.create(); + sandbox.stub(events, 'on').callsFake((event, handler) => { + if (!eventHandlers.hasOwnProperty(event)) { + eventHandlers[event] = []; + } + eventHandlers[event].push(handler); + }); }); - it('should use only valid modules', function () { - rtdModule.init(config); - expect(rtdModule.subModules).to.eql([validSMWait, validSM]); + after(() => { + sandbox.restore(); }); - it('should be able to modify bid request', function (done) { - rtdModule.setBidRequestsData(() => { - assert(getBidRequestDataSpy.calledTwice); - assert(getBidRequestDataSpy.calledWith({bidRequest: {}})); + describe('', () => { + const PROVIDERS = [validSM, invalidSM, failureSM, nonConfSM, validSMWait]; + let _detachers; + + beforeEach(function () { + _detachers = PROVIDERS.map(rtdModule.attachRealTimeDataProvider); + rtdModule.init(config); + config.setConfig(conf); + }); + + afterEach(function () { + _detachers.forEach((f) => f()); + config.resetConfig(); + }); + + it('should use only valid modules', function () { + expect(rtdModule.subModules).to.eql([validSMWait, validSM]); + }); + + it('should be able to modify bid request', function (done) { + rtdModule.setBidRequestsData(() => { + assert(getBidRequestDataSpy.calledTwice); + assert(getBidRequestDataSpy.calledWith({bidRequest: {}})); + done(); + }, {bidRequest: {}}) + }); + + it('sould place targeting on adUnits', function (done) { + const auction = { + adUnitCodes: ['ad1', 'ad2'], + adUnits: [ + { + code: 'ad1' + }, + { + code: 'ad2', + adserverTargeting: {preKey: 'preValue'} + } + ] + }; + + const expectedAdUnits = [ + { + code: 'ad1', + adserverTargeting: {key: 'validSMWait'} + }, + { + code: 'ad2', + adserverTargeting: { + preKey: 'preValue', + key: 'validSM' + } + } + ]; + + const adUnits = rtdModule.getAdUnitTargeting(auction); + assert.deepEqual(expectedAdUnits, adUnits) done(); - }, {bidRequest: {}}) + }); + + describe('setBidRequestData', () => { + let withWait, withoutWait; + + function runSetBidRequestData() { + return new Promise((resolve) => { + rtdModule.setBidRequestsData(resolve, {bidRequest: {}}); + }); + } + + beforeEach(() => { + withWait = { + submod: validSMWait, + cbTime: 0, + cbRan: false + }; + withoutWait = { + submod: validSM, + cbTime: 0, + cbRan: false + }; + + [withWait, withoutWait].forEach((c) => { + c.submod.getBidRequestData = sinon.stub().callsFake((_, cb) => { + setTimeout(() => { + c.cbRan = true; + cb(); + }, c.cbTime); + }); + }); + }); + + it('should allow non-priority submodules to run synchronously', () => { + withWait.cbTime = withoutWait.cbTime = 0; + return runSetBidRequestData().then(() => { + expect(withWait.cbRan).to.be.true; + expect(withoutWait.cbRan).to.be.true; + }) + }); + + it('should not wait for non-priority submodules if priority ones complete first', () => { + withWait.cbTime = 10; + withoutWait.cbTime = 100; + return runSetBidRequestData().then(() => { + expect(withWait.cbRan).to.be.true; + expect(withoutWait.cbRan).to.be.false; + }); + }); + }); }); it('deep merge object', function () { @@ -125,36 +231,78 @@ describe('Real time module', function () { assert.deepEqual(expected, merged); }); - it('sould place targeting on adUnits', function (done) { - const auction = { - adUnitCodes: ['ad1', 'ad2'], - adUnits: [ - { - code: 'ad1' - }, - { - code: 'ad2', - adserverTargeting: {preKey: 'preValue'} - } - ] + describe('event', () => { + const EVENTS = { + [CONSTANTS.EVENTS.AUCTION_INIT]: 'onAuctionInitEvent', + [CONSTANTS.EVENTS.AUCTION_END]: 'onAuctionEndEvent', + [CONSTANTS.EVENTS.BID_RESPONSE]: 'onBidResponseEvent', + [CONSTANTS.EVENTS.BID_REQUESTED]: 'onBidRequestEvent' + } + const conf = { + 'realTimeData': { + dataProviders: [ + { + 'name': 'tp1', + }, + { + 'name': 'tp2', + } + ] + } }; + let providers; + let _detachers; - const expectedAdUnits = [ - { - code: 'ad1', - adserverTargeting: {key: 'validSMWait'} - }, - { - code: 'ad2', - adserverTargeting: { - preKey: 'preValue', - key: 'validSM' - } + function eventHandlingProvider(name) { + const provider = { + name: name, + init: () => true, } - ]; + Object.values(EVENTS).forEach((ev) => provider[ev] = sinon.spy()); + return provider; + } + + beforeEach(() => { + providers = [eventHandlingProvider('tp1'), eventHandlingProvider('tp2')]; + _detachers = providers.map(rtdModule.attachRealTimeDataProvider); + rtdModule.init(config); + config.setConfig(conf); + }); - const adUnits = rtdModule.getAdUnitTargeting(auction); - assert.deepEqual(expectedAdUnits, adUnits) - done(); - }) + afterEach(() => { + _detachers.forEach((d) => d()) + config.resetConfig(); + }); + + it('should set targeting for auctionEnd', () => { + providers.forEach(p => p.getTargetingData = sinon.spy()); + const auction = { + adUnitCodes: ['a1'], + adUnits: [{code: 'a1'}] + }; + mockEmitEvent(CONSTANTS.EVENTS.AUCTION_END, auction); + providers.forEach(p => { + expect(p.getTargetingData.calledWith(auction.adUnitCodes)).to.be.true; + }); + }); + + Object.entries(EVENTS).forEach(([event, hook]) => { + it(`'${event}' should be propagated to providers through '${hook}'`, () => { + const eventArg = {}; + mockEmitEvent(event, eventArg); + providers.forEach((provider) => { + const providerConf = conf.realTimeData.dataProviders.find((cfg) => cfg.name === provider.name); + expect(provider[hook].called).to.be.true; + expect(provider[hook].args).to.have.length(1); + expect(provider[hook].args[0]).to.include.members([eventArg, providerConf]) + }) + }); + + it(`${event} should not fail to propagate elsewhere if a provider throws in its event handler`, () => { + providers[0][hook] = function () { throw new Error() }; + mockEmitEvent(event); + expect(providers[1][hook].called).to.be.true; + }); + }); + }); }); diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 868b1856c34..51850e5f357 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -57,23 +57,34 @@ describe('RelaidoAdapter', function () { serverResponse = { body: { status: 'ok', - price: 500, - model: 'vcpm', - currency: 'JPY', - creativeId: 1000, - uuid: relaido_uuid, - vast: '', + ads: [{ + placementId: 100000, + width: 640, + height: 360, + bidId: '2ed93003f7bb99', + price: 500, + model: 'vcpm', + currency: 'JPY', + creativeId: 1000, + vast: '', + syncUrl: 'https://relaido/sync.html', + adomain: ['relaido.co.jp', 'www.cmertv.co.jp'], + mediaType: 'video' + }], playerUrl: 'https://relaido/player.js', - syncUrl: 'https://relaido/sync.html', - adomain: ['relaido.co.jp', 'www.cmertv.co.jp'] + syncUrl: 'https://api-dev.ulizaex.com/tr/v1/prebid/sync.html', + uuid: relaido_uuid, } }; serverRequest = { - method: 'GET', - bidId: bidRequest.bidId, - width: bidRequest.mediaTypes.video.playerSize[0][0], - height: bidRequest.mediaTypes.video.playerSize[0][1], - mediaType: 'video', + method: 'POST', + data: { + bids: [{ + bidId: bidRequest.bidId, + width: bidRequest.mediaTypes.video.playerSize[0][0], + height: bidRequest.mediaTypes.video.playerSize[0][1], + mediaType: 'video'}] + } }; }); @@ -195,28 +206,23 @@ describe('RelaidoAdapter', function () { describe('spec.buildRequests', function () { it('should build bid requests by video', function () { const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - expect(request.method).to.equal('GET'); - expect(request.url).to.equal('https://api.relaido.jp/bid/v1/prebid/100000'); - expect(request.bidId).to.equal(bidRequest.bidId); - expect(request.width).to.equal(bidRequest.mediaTypes.video.playerSize[0][0]); - expect(request.height).to.equal(bidRequest.mediaTypes.video.playerSize[0][1]); - expect(request.mediaType).to.equal('video'); - expect(request.data.ref).to.equal(bidderRequest.refererInfo.referer); - expect(request.data.timeout_ms).to.equal(bidderRequest.timeout); - expect(request.data.ad_unit_code).to.equal(bidRequest.adUnitCode); - expect(request.data.auction_id).to.equal(bidRequest.auctionId); - expect(request.data.bidder).to.equal(bidRequest.bidder); - expect(request.data.bidder_request_id).to.equal(bidRequest.bidderRequestId); - expect(request.data.bid_requests_count).to.equal(bidRequest.bidRequestsCount); - expect(request.data.bid_id).to.equal(bidRequest.bidId); - expect(request.data.transaction_id).to.equal(bidRequest.transactionId); - expect(request.data.media_type).to.equal('video'); - expect(request.data.uuid).to.equal(relaido_uuid); - expect(request.data.width).to.equal(bidRequest.mediaTypes.video.playerSize[0][0]); - expect(request.data.height).to.equal(bidRequest.mediaTypes.video.playerSize[0][1]); - expect(request.data.pv).to.equal('$prebid.version$'); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const request = data.bids[0]; + expect(bidRequests.method).to.equal('POST'); + expect(bidRequests.url).to.equal('https://api.relaido.jp/bid/v1/sprebid'); + expect(data.ref).to.equal(bidderRequest.refererInfo.referer); + expect(data.timeout_ms).to.equal(bidderRequest.timeout); + expect(request.ad_unit_code).to.equal(bidRequest.adUnitCode); + expect(request.auction_id).to.equal(bidRequest.auctionId); + expect(data.bidder).to.equal(bidRequest.bidder); + expect(request.bidder_request_id).to.equal(bidRequest.bidderRequestId); + expect(data.bid_requests_count).to.equal(bidRequest.bidRequestsCount); + expect(request.bid_id).to.equal(bidRequest.bidId); + expect(request.transaction_id).to.equal(bidRequest.transactionId); + expect(request.media_type).to.equal('video'); + expect(data.uuid).to.equal(relaido_uuid); + expect(data.pv).to.equal('$prebid.version$'); }); it('should build bid requests by banner', function () { @@ -236,9 +242,10 @@ describe('RelaidoAdapter', function () { } }; const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - expect(request.mediaType).to.equal('banner'); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const request = data.bids[0]; + expect(request.media_type).to.equal('banner'); }); it('should take 1x1 size', function () { @@ -258,17 +265,18 @@ describe('RelaidoAdapter', function () { } }; const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const request = data.bids[0]; expect(request.width).to.equal(1); }); it('The referrer should be the last', function () { const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - const keys = Object.keys(request.data); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + const keys = Object.keys(data); expect(keys[0]).to.equal('version'); expect(keys[keys.length - 1]).to.equal('ref'); }); @@ -277,9 +285,9 @@ describe('RelaidoAdapter', function () { bidRequest.userId = {} bidRequest.userId.imuid = 'i.tjHcK_7fTcqnbrS_YA2vaw'; const bidRequests = spec.buildRequests([bidRequest], bidderRequest); - expect(bidRequests).to.have.lengthOf(1); - const request = bidRequests[0]; - expect(request.data.imuid).to.equal('i.tjHcK_7fTcqnbrS_YA2vaw'); + const data = JSON.parse(bidRequests.data); + expect(data.bids).to.have.lengthOf(1); + expect(data.imuid).to.equal('i.tjHcK_7fTcqnbrS_YA2vaw'); }); }); @@ -288,29 +296,29 @@ describe('RelaidoAdapter', function () { const bidResponses = spec.interpretResponse(serverResponse, serverRequest); expect(bidResponses).to.have.lengthOf(1); const response = bidResponses[0]; - expect(response.requestId).to.equal(serverRequest.bidId); - expect(response.width).to.equal(serverRequest.width); - expect(response.height).to.equal(serverRequest.height); - expect(response.cpm).to.equal(serverResponse.body.price); - expect(response.currency).to.equal(serverResponse.body.currency); - expect(response.creativeId).to.equal(serverResponse.body.creativeId); - expect(response.vastXml).to.equal(serverResponse.body.vast); - expect(response.meta.advertiserDomains).to.equal(serverResponse.body.adomain); + expect(response.requestId).to.equal(serverRequest.data.bids[0].bidId); + expect(response.width).to.equal(serverRequest.data.bids[0].width); + expect(response.height).to.equal(serverRequest.data.bids[0].height); + expect(response.cpm).to.equal(serverResponse.body.ads[0].price); + expect(response.currency).to.equal(serverResponse.body.ads[0].currency); + expect(response.creativeId).to.equal(serverResponse.body.ads[0].creativeId); + expect(response.vastXml).to.equal(serverResponse.body.ads[0].vast); + expect(response.meta.advertiserDomains).to.equal(serverResponse.body.ads[0].adomain); expect(response.meta.mediaType).to.equal(VIDEO); expect(response.ad).to.be.undefined; }); it('should build bid response by banner', function () { - serverRequest.mediaType = 'banner'; + serverResponse.body.ads[0].mediaType = 'banner'; const bidResponses = spec.interpretResponse(serverResponse, serverRequest); expect(bidResponses).to.have.lengthOf(1); const response = bidResponses[0]; - expect(response.requestId).to.equal(serverRequest.bidId); - expect(response.width).to.equal(serverRequest.width); - expect(response.height).to.equal(serverRequest.height); - expect(response.cpm).to.equal(serverResponse.body.price); - expect(response.currency).to.equal(serverResponse.body.currency); - expect(response.creativeId).to.equal(serverResponse.body.creativeId); + expect(response.requestId).to.equal(serverRequest.data.bids[0].bidId); + expect(response.width).to.equal(serverRequest.data.bids[0].width); + expect(response.height).to.equal(serverRequest.data.bids[0].height); + expect(response.cpm).to.equal(serverResponse.body.ads[0].price); + expect(response.currency).to.equal(serverResponse.body.ads[0].currency); + expect(response.creativeId).to.equal(serverResponse.body.ads[0].creativeId); expect(response.vastXml).to.be.undefined; expect(response.ad).to.include(`
`); expect(response.ad).to.include(``); @@ -370,8 +378,8 @@ describe('RelaidoAdapter', function () { it('Should create nurl pixel if bid nurl', function () { let bid = { bidder: bidRequest.bidder, - creativeId: serverResponse.body.creativeId, - cpm: serverResponse.body.price, + creativeId: serverResponse.body.ads[0].creativeId, + cpm: serverResponse.body.ads[0].price, params: [bidRequest.params], auctionId: bidRequest.auctionId, requestId: bidRequest.bidId, diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 00ae55823b0..9e9366072be 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -791,6 +791,46 @@ describe('Richaudience adapter tests', function () { })).to.equal(true); }); + it('should pass schain', function() { + let schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'richaudience.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'richaudience-2.com', + 'sid': '00002', + 'hp': 1 + }] + } + + DEFAULT_PARAMS_NEW_SIZES[0].schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'richaudience.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'richaudience-2.com', + 'sid': '00002', + 'hp': 1 + }] + } + + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('schain').to.deep.equal(schain); + }) + describe('userSync', function () { it('Verifies user syncs iframe include', function () { config.setConfig({ @@ -905,7 +945,7 @@ describe('Richaudience adapter tests', function () { }, [], { consentString: null, referer: 'http://domain.com', - gdprApplies: true + gdprApplies: false }) expect(syncs).to.have.lengthOf(1); expect(syncs[0].type).to.equal('image'); @@ -942,7 +982,7 @@ describe('Richaudience adapter tests', function () { }, [], { consentString: null, referer: 'http://domain.com', - gdprApplies: true + gdprApplies: false }) expect(syncs).to.have.lengthOf(0); }); diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 61b307eef21..211c36a254a 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -257,7 +257,7 @@ describe('riseAdapter', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); for (const request of requests) { expect(request.data).to.be.an('object'); - expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,,,,'); + expect(request.data).to.have.property('schain', '1.0,1!indirectseller.com,00001,1,,,'); } }); diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index d6bee26d73b..f4bcb48474a 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -68,6 +68,7 @@ describe('RTBHouseAdapter', () => { 'params': { 'publisherId': 'PREBID_TEST', 'region': 'prebid-eu', + 'channel': 'Partner_Site - news', 'test': 1 }, 'adUnitCode': 'adunit-code', @@ -101,6 +102,25 @@ describe('RTBHouseAdapter', () => { expect(JSON.parse(builtTestRequest).test).to.equal(1); }); + it('should build channel param into request.site', () => { + let builtTestRequest = spec.buildRequests(bidRequests, bidderRequest).data; + expect(JSON.parse(builtTestRequest).site.channel).to.equal('Partner_Site - news'); + }) + + it('should not build channel param into request.site if no value is passed', () => { + let bidRequest = Object.assign([], bidRequests); + bidRequest[0].params.channel = undefined; + let builtTestRequest = spec.buildRequests(bidRequest, bidderRequest).data; + expect(JSON.parse(builtTestRequest).site.channel).to.be.undefined + }) + + it('should cap the request.site.channel length to 50', () => { + let bidRequest = Object.assign([], bidRequests); + bidRequest[0].params.channel = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent scelerisque ipsum eu purus lobortis iaculis.'; + let builtTestRequest = spec.buildRequests(bidRequest, bidderRequest).data; + expect(JSON.parse(builtTestRequest).site.channel.length).to.equal(50) + }) + it('should build valid OpenRTB banner object', () => { const request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); const imp = request.imp[0]; diff --git a/test/spec/modules/rubiconAnalyticsAdapter_spec.js b/test/spec/modules/rubiconAnalyticsAdapter_spec.js index 798e98bb97d..ce41e99b8fd 100644 --- a/test/spec/modules/rubiconAnalyticsAdapter_spec.js +++ b/test/spec/modules/rubiconAnalyticsAdapter_spec.js @@ -987,6 +987,36 @@ describe('rubicon analytics adapter', function () { expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; }); + it('should NOT pass along adomians with other edge cases', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // should filter out non string values and pass valid ones + let bidResponse1 = utils.deepClone(MOCK.BID_RESPONSE[0]); + bidResponse1.meta = { + advertiserDomains: [123, 'prebid.org', false, true, [], 'magnite.com', {}] + } + + // array of arrays (as seen when passed by kargo bid adapter) + let bidResponse2 = utils.deepClone(MOCK.BID_RESPONSE[1]); + bidResponse2.meta = { + advertiserDomains: [['prebid.org']] + } + + events.emit(BID_RESPONSE, bidResponse1); + events.emit(BID_RESPONSE, bidResponse2); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + let message = JSON.parse(server.requests[0].requestBody); + validate(message); + expect(message.auctions[0].adUnits[0].bids[0].bidResponse.adomains).to.deep.equal(['prebid.org', 'magnite.com']); + expect(message.auctions[0].adUnits[1].bids[0].bidResponse.adomains).to.be.undefined; + }); + it('should not pass empty adServerTargeting values', function () { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 6210640f79f..ea839bc7ae3 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1408,7 +1408,7 @@ describe('the rubicon adapter', function () { expect(data['tg_i.pbadslot']).to.equal('abc'); }); - it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string, but all leading slash characters should be removed', function () { + it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { bidderRequest.bids[0].ortb2Imp = { ext: { data: { @@ -1422,7 +1422,45 @@ describe('the rubicon adapter', function () { expect(data).to.be.an('Object'); expect(data).to.have.property('tg_i.pbadslot'); - expect(data['tg_i.pbadslot']).to.equal('a/b/c'); + expect(data['tg_i.pbadslot']).to.equal('/a/b/c'); + }); + + it('should send gpid as p_gpid if valid', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + gpid: '/1233/sports&div1' + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('p_gpid'); + expect(data['p_gpid']).to.equal('/1233/sports&div1'); + }); + + it('should send gpid and pbadslot since it is prefered over dfp code', function () { + bidderRequest.bids[0].ortb2Imp = { + ext: { + gpid: '/1233/sports&div1', + data: { + pbadslot: 'pb_slot', + adserver: { + adslot: '/1234/sports', + name: 'gam' + } + } + } + } + + const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data['p_gpid']).to.equal('/1233/sports&div1'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(data['tg_i.pbadslot']).to.equal('pb_slot'); }); }); @@ -1470,12 +1508,13 @@ describe('the rubicon adapter', function () { expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string', function () { + it('should send NOT \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string but not gam', function () { bidderRequest.bids[0].ortb2Imp = { ext: { data: { adserver: { - adslot: 'abc' + adslot: '/a/b/c', + name: 'not gam' } } } @@ -1485,16 +1524,16 @@ describe('the rubicon adapter', function () { const data = parseQuery(request.data); expect(data).to.be.an('Object'); - expect(data).to.have.property('tg_i.dfp_ad_unit_code'); - expect(data['tg_i.dfp_ad_unit_code']).to.equal('abc'); + expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); }); - it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string, but all leading slash characters should be removed', function () { + it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string and name is gam', function () { bidderRequest.bids[0].ortb2Imp = { ext: { data: { adserver: { - adslot: 'a/b/c' + name: 'gam', + adslot: '/a/b/c' } } } @@ -1505,7 +1544,7 @@ describe('the rubicon adapter', function () { expect(data).to.be.an('Object'); expect(data).to.have.property('tg_i.dfp_ad_unit_code'); - expect(data['tg_i.dfp_ad_unit_code']).to.equal('a/b/c'); + expect(data['tg_i.dfp_ad_unit_code']).to.equal('/a/b/c'); }); }); }); @@ -3343,6 +3382,23 @@ describe('the rubicon adapter', function () { type: 'iframe', url: `${emilyUrl}?gdpr_consent=foo&us_privacy=1NYN` }); }); + + it('should pass gdprApplies', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { + gdprApplies: true + }, '1NYN')).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gdpr=1&us_privacy=1NYN` + }); + }); + + it('should pass all correctly', function () { + expect(spec.getUserSyncs({iframeEnabled: true}, {}, { + gdprApplies: true, + consentString: 'foo' + }, '1NYN')).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` + }); + }); }); describe('get price granularity', function () { diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 3aa378379dd..159645ff6d6 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -274,6 +274,9 @@ describe('Seedtag Adapter', function() { expect(data.ga).to.equal(true) expect(data.cd).to.equal('consentString') }) + it('should expose gvlid', function() { + expect(spec.gvlid).to.equal(157) + }) }) }) diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index 534d0b3f381..8ef34a1599e 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -50,10 +50,10 @@ describe('SharedId System', function () { expect(callbackSpy.calledOnce).to.be.true; expect(callbackSpy.lastCall.lastArg).to.equal(UUID); }); - it('should log message if coppa is set', function () { + it('should abort if coppa is set', function () { coppaDataHandlerDataStub.returns('true'); - sharedIdSystemSubmodule.getId({}); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); + const result = sharedIdSystemSubmodule.getId({}); + expect(result).to.be.undefined; }); }); describe('SharedId System extendId()', function () { @@ -85,10 +85,56 @@ describe('SharedId System', function () { let pubcommId = sharedIdSystemSubmodule.extendId(config, undefined, 'TestId').id; expect(pubcommId).to.equal('TestId'); }); - it('should log message if coppa is set', function () { + it('should abort if coppa is set', function () { coppaDataHandlerDataStub.returns('true'); - sharedIdSystemSubmodule.extendId({}, undefined, 'TestId'); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('PubCommonId: IDs not provided for coppa requests, exiting PubCommonId'); + const result = sharedIdSystemSubmodule.extendId({params: {extend: true}}, undefined, 'TestId'); + expect(result).to.be.undefined; + }); + }); + + describe('SharedID System domainOverride', () => { + let sandbox, domain, cookies, rejectCookiesFor; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(document, 'domain').get(() => domain); + cookies = {}; + sandbox.stub(storage, 'getCookie').callsFake((key) => cookies[key]); + rejectCookiesFor = null; + sandbox.stub(storage, 'setCookie').callsFake((key, value, expires, sameSite, domain) => { + if (domain !== rejectCookiesFor) { + if (expires != null) { + expires = new Date(expires); + } + if (expires == null || expires > Date.now()) { + cookies[key] = value; + } else { + delete cookies[key]; + } + } + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return TLD if cookies can be set there', () => { + domain = 'sub.domain.com'; + rejectCookiesFor = 'com'; + expect(sharedIdSystemSubmodule.domainOverride()).to.equal('domain.com'); + }); + + it('should return undefined when cookies cannot be set', () => { + domain = 'sub.domain.com'; + rejectCookiesFor = 'sub.domain.com'; + expect(sharedIdSystemSubmodule.domainOverride()).to.be.undefined; + }); + + it('should return half-way domain if parent domain rejects cookies', () => { + domain = 'inner.outer.domain.com'; + rejectCookiesFor = 'domain.com'; + expect(sharedIdSystemSubmodule.domainOverride()).to.equal('outer.domain.com'); }); }); }); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index db21af5f6b3..39238cc877f 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import * as sinon from 'sinon'; import { sharethroughAdapterSpec, sharethroughInternal } from 'modules/sharethroughBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config'; @@ -157,7 +158,6 @@ describe('sharethrough adapter spec', function () { startdelay: 42, skipmin: 10, skipafter: 20, - placement: 1, delivery: 1, companiontype: 'companion type', companionad: 'companion ad', @@ -205,6 +205,7 @@ describe('sharethrough adapter spec', function () { expect(openRtbReq.cur).to.deep.equal(['USD']); expect(openRtbReq.tmax).to.equal(242); + expect(Object.keys(openRtbReq.site)).to.have.length(3); expect(openRtbReq.site.domain).not.to.be.undefined; expect(openRtbReq.site.page).not.to.be.undefined; expect(openRtbReq.site.ref).to.equal('https://referer.com'); @@ -256,6 +257,17 @@ describe('sharethrough adapter spec', function () { }); }); + describe('no referer provided', () => { + beforeEach(() => { + bidderRequest = {}; + }); + + it('should set referer to undefined', () => { + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; + expect(openRtbReq.site.ref).to.be.undefined; + }); + }); + describe('regulation', () => { describe('gdpr', () => { it('should populate request accordingly when gdpr applies', () => { @@ -419,17 +431,90 @@ describe('sharethrough adapter spec', function () { expect(videoImp.startdelay).to.equal(0); expect(videoImp.skipmin).to.equal(0); expect(videoImp.skipafter).to.equal(0); - expect(videoImp.placement).to.be.undefined; + expect(videoImp.placement).to.equal(1); expect(videoImp.delivery).to.be.undefined; expect(videoImp.companiontype).to.be.undefined; expect(videoImp.companionad).to.be.undefined; }); - it('should not return a video impression if context is outstream', () => { - bidRequests[1].mediaTypes.video.context = 'outstream'; - const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + describe('outstream', () => { + it('should use placement value if provided', () => { + bidRequests[1].mediaTypes.video.context = 'outstream'; + bidRequests[1].mediaTypes.video.placement = 3; + + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + const videoImp = builtRequest.data.imp[0].video; + + expect(videoImp.placement).to.equal(3); + }); + + it('should default placement to 4 if not provided', () => { + bidRequests[1].mediaTypes.video.context = 'outstream'; + + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + const videoImp = builtRequest.data.imp[0].video; + + expect(videoImp.placement).to.equal(4); + }); + }); + }); + + describe('first party data', () => { + const firstPartyData = { + site: { + name: 'example', + keywords: 'power tools, drills', + search: 'drill', + content: { + userrating: '4', + }, + ext: { + data: { + pageType: 'article', + category: 'repair', + }, + }, + }, + user: { + yob: 1985, + gender: 'm', + ext: { + data: { + registered: true, + interests: ['cars'], + }, + }, + }, + }; + + let configStub; + + beforeEach(() => { + configStub = sinon.stub(config, 'getConfig'); + configStub.withArgs('ortb2').returns(firstPartyData); + }); + + afterEach(() => { + configStub.restore(); + }); + + it('should include first party data in open rtb request, site section', () => { + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; + + expect(openRtbReq.site.name).to.equal(firstPartyData.site.name); + expect(openRtbReq.site.keywords).to.equal(firstPartyData.site.keywords); + expect(openRtbReq.site.search).to.equal(firstPartyData.site.search); + expect(openRtbReq.site.content).to.deep.equal(firstPartyData.site.content); + expect(openRtbReq.site.ext).to.deep.equal(firstPartyData.site.ext); + }); + + it('should include first party data in open rtb request, user section', () => { + const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; - expect(builtRequest).to.be.undefined; + expect(openRtbReq.user.yob).to.equal(firstPartyData.user.yob); + expect(openRtbReq.user.gender).to.equal(firstPartyData.user.gender); + expect(openRtbReq.user.ext.data).to.deep.equal(firstPartyData.user.ext.data); + expect(openRtbReq.user.ext.eids).not.to.be.undefined; }); }); }); diff --git a/test/spec/modules/showheroes-bsBidAdapter_spec.js b/test/spec/modules/showheroes-bsBidAdapter_spec.js index ad2210c18c6..69e8343dfc9 100644 --- a/test/spec/modules/showheroes-bsBidAdapter_spec.js +++ b/test/spec/modules/showheroes-bsBidAdapter_spec.js @@ -13,6 +13,7 @@ const adomain = ['showheroes.com']; const gdpr = { 'gdprConsent': { + 'apiVersion': 2, 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', 'gdprApplies': true } @@ -332,6 +333,7 @@ describe('shBidAdapter', function () { { 'cpm': 5, 'creativeId': 'c_38b373e1e31c18', + 'adUnitCode': 'adunit-code-1', 'currency': 'EUR', 'width': 640, 'height': 480, diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index faa288306a5..9803d1df103 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -287,6 +287,23 @@ describe('smaatoBidAdapterTest', () => { expect(req.regs.ext.us_privacy).to.equal('uspConsentString'); }); + it('sends no schain if no schain exists', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.source.ext.schain).to.not.exist; + }); + + it('sends instl if instl exists', () => { + const instl = { instl: 1 }; + const bidRequestWithInstl = Object.assign({}, singleBannerBidRequest, {ortb2Imp: instl}); + + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + }); + it('sends tmax', () => { const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); @@ -435,6 +452,16 @@ describe('smaatoBidAdapterTest', () => { expect(req.imp[0].bidfloor).to.be.equal(0.456); }); + it('sends instl if instl exists', () => { + const instl = { instl: 1 }; + const bidRequestWithInstl = Object.assign({}, singleVideoBidRequest, {ortb2Imp: instl}); + + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + }); + it('splits multi format bid requests', () => { const combinedBannerAndVideoBidRequest = { bidder: 'smaato', @@ -516,6 +543,17 @@ describe('smaatoBidAdapterTest', () => { expect(req.imp[1].video.sequence).to.be.equal(2); }); + it('sends instl if instl exists', () => { + const instl = { instl: 1 }; + const bidRequestWithInstl = Object.assign({}, longFormVideoBidRequest, {ortb2Imp: instl}); + + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + expect(req.imp[1].instl).to.equal(1); + }); + it('sends bidfloor when configured', () => { const longFormVideoBidRequestWithFloor = Object.assign({}, longFormVideoBidRequest); longFormVideoBidRequestWithFloor.getFloor = function(arg) { @@ -854,6 +892,29 @@ describe('smaatoBidAdapterTest', () => { expect(req.user.ext.eids).to.have.length(2); }); }); + + describe('schain in request', () => { + it('schain is added to source.ext.schain', () => { + const schain = { + ver: '1.0', + complete: 1, + nodes: [ + { + 'asi': 'asi', + 'sid': 'sid', + 'rid': 'rid', + 'hp': 1 + } + ] + }; + const bidRequestWithSchain = Object.assign({}, singleBannerBidRequest, {schain: schain}); + + const reqs = spec.buildRequests([bidRequestWithSchain], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.source.ext.schain).to.deep.equal(schain); + }); + }); }); describe('interpretResponse', () => { diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index 4e560c87df3..ddee2fa3347 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -189,6 +189,14 @@ describe('The smartx adapter', function () { domain: '', publisher: { id: '__name__' + }, + content: { + ext: { + prebid: { + name: 'pbjs', + version: '$prebid.version$' + } + } } }); }); @@ -525,6 +533,7 @@ describe('The smartx adapter', function () { bidderRequestObj.bidRequest.bids[0].params.outstream_options.title = 'abc'; bidderRequestObj.bidRequest.bids[0].params.outstream_options.skipOffset = 2; bidderRequestObj.bidRequest.bids[0].params.outstream_options.desiredBitrate = 123; + bidderRequestObj.bidRequest.bids[0].params.outstream_options.visibilityThreshold = 30; responses[0].renderer.render(responses[0]); diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js index 75a269c93f7..d9f3ca84a3a 100644 --- a/test/spec/modules/sspBCBidAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -518,6 +518,33 @@ describe('SSPBC adapter', function () { expect(payload.user).to.be.an('object').and.to.have.property('[ortb_extensions.consent]', bidRequest.gdprConsent.consentString); }); + it('should send net info and pvid', function () { + expect(payload.user).to.be.an('object').and.to.have.property('data').that.is.an('array'); + + const userData = payload.user.data; + expect(userData.length).to.equal(2); + + const netInfo = userData[0]; + expect(netInfo.id).to.equal('12'); + expect(netInfo.name).to.equal('NetInfo'); + expect(netInfo).to.have.property('segment').that.is.an('array'); + + const pvid = userData[1]; + expect(pvid.id).to.equal('7'); + expect(pvid.name).to.equal('pvid'); + expect(pvid).to.have.property('segment').that.is.an('array'); + expect(pvid.segment[0]).to.have.property('value'); + }); + + it('pvid should be constant on a single page view', function () { + const userData1 = payload.user.data; + const userData2 = payloadNative.user.data; + const pvid1 = userData1[1]; + const pvid2 = userData2[1]; + + expect(pvid1.segment[0].value).to.equal(pvid2.segment[0].value); + }); + it('should build correct native payload', function () { const nativeAssets = payloadNative.imp && payloadNative.imp[0].native.request; @@ -543,13 +570,16 @@ describe('SSPBC adapter', function () { expect(videoAssets).to.have.property('api').that.is.an('array'); }); - it('should create auxilary placement identifier (size_numUsed)', function () { + it('should create auxilary placement identifier (size_numUsed), that is constant for a given adUnit', function () { const extAssets1 = payload.imp && payload.imp[0].ext.data; const extAssets2 = payloadSingle.imp && payloadSingle.imp[0].ext.data; - // note that payload comes from first, and payloadSingle from second auction in the test run + /* + note that payload comes from first, and payloadSingle from second auction in the test run + also, since both have same adUnitName, value of pbsize property should be the same + */ expect(extAssets1).to.have.property('pbsize').that.equals('750x200_1') - expect(extAssets2).to.have.property('pbsize').that.equals('750x200_2') + expect(extAssets2).to.have.property('pbsize').that.equals('750x200_1') }); }); diff --git a/test/spec/modules/synacormediaBidAdapter_spec.js b/test/spec/modules/synacormediaBidAdapter_spec.js index 5f3633ec311..c053771c296 100644 --- a/test/spec/modules/synacormediaBidAdapter_spec.js +++ b/test/spec/modules/synacormediaBidAdapter_spec.js @@ -1231,4 +1231,74 @@ describe('synacormediaBidAdapter ', function () { expect(usersyncs).to.be.an('array').that.is.empty; }); }); + + describe('Bid Requests with price module should use if available', function () { + let validVideoBidRequest = { + bidder: 'synacormedia', + params: { + bidfloor: '0.50', + seatId: 'prebid', + placementId: 'demo1', + pos: 1, + video: {} + }, + renderer: { + url: '../syncOutstreamPlayer.js' + }, + mediaTypes: { + video: { + playerSize: [[300, 250]], + context: 'outstream' + } + }, + adUnitCode: 'div-1', + transactionId: '0869f34e-090b-4b20-84ee-46ff41405a39', + sizes: [[300, 250]], + bidId: '22b3a2268d9f0e', + bidderRequestId: '1d195910597e13', + auctionId: '3375d336-2aea-4ee7-804c-6d26b621ad20', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }; + + let validBannerBidRequest = { + bidId: '9876abcd', + sizes: [[300, 250]], + params: { + bidfloor: '0.50', + seatId: 'prebid', + placementId: '1234', + } + }; + + let bidderRequest = { + refererInfo: { + referer: 'http://localhost:9999/' + }, + bidderCode: 'synacormedia', + auctionId: 'f8a75621-d672-4cbb-9275-3db7d74fb110' + }; + + it('should return valid bidfloor using price module for banner/video impression', function () { + let bannerRequest = spec.buildRequests([validBannerBidRequest], bidderRequest); + let videoRequest = spec.buildRequests([validVideoBidRequest], bidderRequest); + + expect(bannerRequest.data.imp[0].bidfloor).to.equal(0.5); + expect(videoRequest.data.imp[0].bidfloor).to.equal(0.5); + + let priceModuleFloor = 3; + let floorResponse = { currency: 'USD', floor: priceModuleFloor }; + + validBannerBidRequest.getFloor = () => { return floorResponse; }; + validVideoBidRequest.getFloor = () => { return floorResponse; }; + + bannerRequest = spec.buildRequests([validBannerBidRequest], bidderRequest); + videoRequest = spec.buildRequests([validVideoBidRequest], bidderRequest); + + expect(bannerRequest.data.imp[0].bidfloor).to.equal(priceModuleFloor); + expect(videoRequest.data.imp[0].bidfloor).to.equal(priceModuleFloor); + }); + }); }); diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js index 3fe691847b6..8866670df77 100644 --- a/test/spec/modules/tappxBidAdapter_spec.js +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -134,12 +134,6 @@ describe('Tappx bid adapter', function () { assert.isTrue(spec.isBidRequestValid(c_BIDREQUEST.bids[0]), JSON.stringify(c_BIDREQUEST)); }); - it('should return false when params are missing', function () { - let badBidRequestParam = JSON.parse(JSON.stringify(c_BIDREQUEST)); - delete badBidRequestParam.bids[0].params; - assert.isFalse(spec.isBidRequestValid(badBidRequestParam.bids[0])); - }); - it('should return false when tappxkey is missing', function () { let badBidRequestTpxkey = JSON.parse(JSON.stringify(c_BIDREQUEST)); ; delete badBidRequestTpxkey.bids[0].params.tappxkey; @@ -165,24 +159,18 @@ describe('Tappx bid adapter', function () { assert.isTrue(spec.isBidRequestValid(badBidRequestNwEp.bids[0])); }); - it('should return false mimes param is missing', function () { - let badBidRequest_mimes = c_BIDDERREQUEST_V; - delete badBidRequest_mimes.bids.mediaTypes.video; - badBidRequest_mimes.bids.mediaTypes.video = {}; - badBidRequest_mimes.bids.mediaTypes.video.context = 'instream'; - badBidRequest_mimes.bids.mediaTypes.video.playerSize = [320, 250]; - assert.isFalse(spec.isBidRequestValid(badBidRequest_mimes.bids), badBidRequest_mimes); - }); - it('should return false for not instream/outstream requests', function () { let badBidRequest_v = c_BIDDERREQUEST_V; delete badBidRequest_v.bids.mediaTypes.banner; badBidRequest_v.bids.mediaTypes.video = {}; badBidRequest_v.bids.mediaTypes.video.context = ''; - badBidRequest_v.bids.mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; badBidRequest_v.bids.mediaTypes.video.playerSize = [320, 250]; assert.isFalse(spec.isBidRequestValid(badBidRequest_v.bids)); }); + + it('should export the TCF vendor ID', function () { + expect(spec.gvlid).to.equal(628); + }) }); /** @@ -228,7 +216,6 @@ describe('Tappx bid adapter', function () { validBidRequests_V[0].mediaTypes.video = {}; validBidRequests_V[0].mediaTypes.video.playerSize = [640, 480]; validBidRequests_V[0].mediaTypes.video.context = 'instream'; - validBidRequests_V[0].mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; bidderRequest_V.bids.mediaTypes.context = 'instream'; @@ -248,7 +235,6 @@ describe('Tappx bid adapter', function () { validBidRequests_Voutstream[0].mediaTypes.video = {}; validBidRequests_Voutstream[0].mediaTypes.video.playerSize = [640, 480]; validBidRequests_Voutstream[0].mediaTypes.video.context = 'outstream'; - validBidRequests_Voutstream[0].mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; bidderRequest_VOutstream.bids.mediaTypes.context = 'outstream'; @@ -269,7 +255,6 @@ describe('Tappx bid adapter', function () { validBidRequests_Voutstream[0].mediaTypes.video.rewarded = 1; validBidRequests_Voutstream[0].mediaTypes.video.playerSize = [640, 480]; validBidRequests_Voutstream[0].mediaTypes.video.context = 'outstream'; - validBidRequests_Voutstream[0].mediaTypes.video.mimes = [ 'video/mp4', 'application/javascript' ]; bidderRequest_VOutstream.bids.mediaTypes.context = 'outstream'; diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js new file mode 100644 index 00000000000..0ce6f0fb70d --- /dev/null +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -0,0 +1,96 @@ +import { spec } from '../../../modules/targetVideoBidAdapter.js' + +describe('TargetVideo Bid Adapter', function() { + const bannerRequest = [{ + bidder: 'targetVideo', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + params: { + placementId: 12345, + } + }]; + + it('Test the bid validation function', function() { + const validBid = spec.isBidRequestValid(bannerRequest[0]); + const invalidBid = spec.isBidRequestValid(null); + + expect(validBid).to.be.true; + expect(invalidBid).to.be.false; + }); + + it('Test the request processing function', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + expect(request).to.not.be.empty; + + const payload = JSON.parse(request.data); + expect(payload).to.not.be.empty; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + expect(payload.tags[0].id).to.equal(12345); + expect(payload.tags[0].gpid).to.equal('targetVideo'); + expect(payload.tags[0].ad_types[0]).to.equal('video'); + }); + + it('Handle nobid responses', function () { + const responseBody = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] + }; + const bidderRequest = null; + + const bidResponse = spec.interpretResponse({ body: responseBody }, {bidderRequest}); + expect(bidResponse.length).to.equal(0); + }); + + it('Test the response parsing function', function () { + const responseBody = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'https://www.target-video.com/', + 'rtb': { + 'video': { + 'player_width': 640, + 'player_height': 360, + 'asset_url': 'https://www.target-video.com/' + } + } + }] + }] + }; + const bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }] + }; + + const bidResponse = spec.interpretResponse({ body: responseBody }, {bidderRequest}); + expect(bidResponse).to.not.be.empty; + + const bid = bidResponse[0]; + expect(bid).to.not.be.empty; + expect(bid.cpm).to.equal(0.675); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ad).to.include('') + expect(bid.ad).to.include('initPlayer') + }); +}); diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 83f5045cca1..75ed7452b57 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -614,15 +614,13 @@ describe('teadsBidAdapter', () => { } ]; - it('should add gpid if ortb2Imp.ext.data.pbadslot is present and is non empty (and ortb2Imp.ext.data.adserver.adslot is not present)', function () { + it('should add gpid if ortb2Imp.ext.gpid is present and is non empty', function () { const updatedBidRequests = bidRequests.map(function(bidRequest, index) { return { ...bidRequest, ortb2Imp: { ext: { - data: { - pbadslot: '1111/home-left-' + index - } + gpid: '1111/home-left-' + index } } }; @@ -635,41 +633,12 @@ describe('teadsBidAdapter', () => { expect(payload.data[1].gpid).to.equal('1111/home-left-1'); }); - it('should add gpid if ortb2Imp.ext.data.pbadslot is present and is non empty (even if ortb2Imp.ext.data.adserver.adslot is present and is non empty too)', function () { - const updatedBidRequests = bidRequests.map(function(bidRequest, index) { - return { - ...bidRequest, - ortb2Imp: { - ext: { - data: { - pbadslot: '1111/home-left-' + index, - adserver: { - adslot: '1111/home-left/div-' + index - } - } - } - } - }; - } - ); - const request = spec.buildRequests(updatedBidRequests, bidderResquestDefault); - const payload = JSON.parse(request.data); - - expect(payload.data[0].gpid).to.equal('1111/home-left-0'); - expect(payload.data[1].gpid).to.equal('1111/home-left-1'); - }); - - it('should not add gpid if both ortb2Imp.ext.data.pbadslot and ortb2Imp.ext.data.adserver.adslot are present but empty', function () { + it('should not add gpid if ortb2Imp.ext.gpid is present but empty', function () { const updatedBidRequests = bidRequests.map(bidRequest => ({ ...bidRequest, ortb2Imp: { ext: { - data: { - pbadslot: '', - adserver: { - adslot: '' - } - } + gpid: '' } } })); @@ -682,36 +651,22 @@ describe('teadsBidAdapter', () => { }); }); - it('should not add gpid if both ortb2Imp.ext.data.pbadslot and ortb2Imp.ext.data.adserver.adslot are not present', function () { - const request = spec.buildRequests(bidRequests, bidderResquestDefault); + it('should not add gpid if ortb2Imp.ext.gpid is not present', function () { + const updatedBidRequests = bidRequests.map(bidRequest => ({ + ...bidRequest, + ortb2Imp: { + ext: { + } + } + })); + + const request = spec.buildRequests(updatedBidRequests, bidderResquestDefault); const payload = JSON.parse(request.data); return payload.data.forEach(bid => { expect(bid).not.to.have.property('gpid'); }); }); - - it('should add gpid if ortb2Imp.ext.data.pbadslot is not present but ortb2Imp.ext.data.adserver.adslot is present and is non empty', function () { - const updatedBidRequests = bidRequests.map(function(bidRequest, index) { - return { - ...bidRequest, - ortb2Imp: { - ext: { - data: { - adserver: { - adslot: '1111/home-left-' + index - } - } - } - } - }; - }); - const request = spec.buildRequests(updatedBidRequests, bidderResquestDefault); - const payload = JSON.parse(request.data); - - expect(payload.data[0].gpid).to.equal('1111/home-left-0'); - expect(payload.data[1].gpid).to.equal('1111/home-left-1'); - }); }); function checkMediaTypesSizes(mediaTypes, expectedSizes) { diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 73bc9d45365..e710bd6d00f 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -519,6 +519,48 @@ describe('TrustXAdapter', function () { expect(payload.tmax).to.equal(3000); getConfigStub.restore(); }); + it('should contain imp[].ext.data.adserver if available', function() { + const ortb2Imp = [{ + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/111111/slot' + }, + pbadslot: '/111111/slot' + } + } + }, { + ext: { + data: { + adserver: { + name: 'ad_server_name', + adslot: '/222222/slot' + }, + pbadslot: '/222222/slot' + } + } + }]; + const bidRequestsWithOrtb2Imp = bidRequests.slice(0, 3).map((bid, ind) => { + return Object.assign(ortb2Imp[ind] ? { ortb2Imp: ortb2Imp[ind] } : {}, bid); + }); + const request = spec.buildRequests(bidRequestsWithOrtb2Imp, bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].ext).to.deep.equal({ + divid: bidRequests[0].adUnitCode, + data: ortb2Imp[0].ext.data, + gpid: ortb2Imp[0].ext.data.adserver.adslot + }); + expect(payload.imp[1].ext).to.deep.equal({ + divid: bidRequests[1].adUnitCode, + data: ortb2Imp[1].ext.data, + gpid: ortb2Imp[1].ext.data.adserver.adslot + }); + expect(payload.imp[2].ext).to.deep.equal({ + divid: bidRequests[2].adUnitCode + }); + }); it('all id like request fields must be a string', function () { const bidderRequestWithNumId = Object.assign({}, bidderRequest, { bidderRequestId: 123123, auctionId: 345345543 }); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 0190bceca70..8ddb02138f5 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -49,6 +49,8 @@ import {flocIdSubmodule} from 'modules/flocIdSystem.js' import {amxIdSubmodule} from '../../../modules/amxIdSystem.js'; import {akamaiDAPIdSubmodule} from 'modules/akamaiDAPIdSystem.js' import {kinessoIdSubmodule} from 'modules/kinessoIdSystem.js' +import {adqueryIdSubmodule} from 'modules/adqueryIdSystem.js'; +import * as mockGpt from '../integration/faker/googletag.js'; let assert = require('chai').assert; let expect = require('chai').expect; @@ -114,15 +116,21 @@ describe('User ID', function () { describe('Decorate Ad Units', function () { beforeEach(function () { + // reset mockGpt so nothing else interferes + mockGpt.disable(); + mockGpt.enable(); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); coreStorage.setCookie('pubcid_alt', 'altpubcid200000', (new Date(Date.now() + 5000).toUTCString())); sinon.spy(coreStorage, 'setCookie'); + sinon.stub(utils, 'logWarn'); }); afterEach(function () { + mockGpt.enable(); $$PREBID_GLOBAL$$.requestBids.removeAll(); config.resetConfig(); coreStorage.setCookie.restore(); + utils.logWarn.restore(); }); after(function () { @@ -321,6 +329,50 @@ describe('User ID', function () { expect((getGlobal()).getUserIdsAsEids()).to.deep.equal(createEidsArray((getGlobal()).getUserIds())); }); + it('should set googletag ppid correctly', function () { + let adUnits = [getAdUnitMock()]; + setSubmoduleRegistry([amxIdSubmodule, sharedIdSystemSubmodule, identityLinkSubmodule]); + init(config); + + config.setConfig({ + userSync: { + ppid: 'pubcid.org', + userIds: [ + { name: 'amxId', value: {'amxId': 'amx-id-value-amx-id-value-amx-id-value'} }, + { name: 'pubCommonId', value: {'pubcid': 'pubCommon-id-value-pubCommon-id-value'} }, + { name: 'identityLink', value: {'idl_env': 'identityLink-id-value-identityLink-id-value'} }, + ] + } + }); + // before ppid should not be set + expect(window.googletag._ppid).to.equal(undefined); + requestBidsHook(() => {}, {adUnits}); + // ppid should have been set without dashes and stuff + expect(window.googletag._ppid).to.equal('pubCommonidvaluepubCommonidvalue'); + }); + + it('should log a warning if PPID too big or small', function () { + let adUnits = [getAdUnitMock()]; + setSubmoduleRegistry([sharedIdSystemSubmodule]); + init(config); + + config.setConfig({ + userSync: { + ppid: 'pubcid.org', + userIds: [ + { name: 'pubCommonId', value: {'pubcid': 'pubcommonIdValue'} }, + ] + } + }); + // before ppid should not be set + expect(window.googletag._ppid).to.equal(undefined); + requestBidsHook(() => {}, {adUnits}); + // ppid should NOT have been set + expect(window.googletag._ppid).to.equal(undefined); + // a warning should have been emmited + expect(utils.logWarn.args[0][0]).to.exist.and.to.contain('User ID - Googletag Publisher Provided ID for pubcid.org is not between 32 and 150 characters - pubcommonIdValue'); + }); + it('pbjs.refreshUserIds refreshes', function() { let sandbox = sinon.createSandbox(); @@ -508,7 +560,7 @@ describe('User ID', function () { }); it('handles config with no usersync object', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' @@ -516,14 +568,14 @@ describe('User ID', function () { }); it('handles config with empty usersync object', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -534,7 +586,7 @@ describe('User ID', function () { }); it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -551,7 +603,7 @@ describe('User ID', function () { }); it('config with 1 configurations should create 1 submodules', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); @@ -572,8 +624,8 @@ describe('User ID', function () { expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 23 configurations should result in 23 submodules add', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + it('config with 24 configurations should result in 24 submodules add', function () { + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -638,14 +690,17 @@ describe('User ID', function () { }, { name: 'kpuid', storage: {name: 'kpuid', type: 'cookie'} + }, { + name: 'qid', + storage: {name: 'qid', type: 'html5'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 23 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 24 submodules'); }); it('config syncDelay updates module correctly', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ @@ -661,7 +716,7 @@ describe('User ID', function () { }); it('config auctionDelay updates module correctly', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -676,7 +731,7 @@ describe('User ID', function () { }); it('config auctionDelay defaults to 0 if not a number', function () { - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, nextrollIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, flocIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ userSync: { @@ -1772,7 +1827,38 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, netId, haloId, Criteo, UID 2.0, admixerId, amxId, dmdId, kpuid and mwOpenLinkId have data to pass', function (done) { + it('test hook from qid html5', (done) => { + // simulate existing localStorage values + localStorage.setItem('qid', 'testqid'); + localStorage.setItem('qid_exp', ''); + + setSubmoduleRegistry([adqueryIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['qid', 'qid', 'html5'])); + + requestBidsHook(() => { + adUnits.forEach((adUnit) => { + adUnit.bids.forEach((bid) => { + expect(bid).to.have.deep.nested.property('userId.qid'); + expect(bid.userId.qid).to.equal('testqid'); + expect(bid.userIdAsEids[0]).to.deep.equal({ + source: 'adquery.io', + uids: [{ + id: 'testqid', + atype: 1, + }] + }); + }); + }); + + // clear LS + localStorage.removeItem('qid'); + localStorage.removeItem('qid_exp'); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, netId, haloId, Criteo, UID 2.0, admixerId, amxId, dmdId, kpuid, qid and mwOpenLinkId have data to pass', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); @@ -1789,12 +1875,13 @@ describe('User ID', function () { coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); - // amxId only supports localStorage localStorage.setItem('amxId', 'test_amxid_id'); localStorage.setItem('amxId_exp', (new Date(Date.now() + 5000)).toUTCString()); - - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + // qid only supports localStorage + localStorage.setItem('qid', 'testqid'); + localStorage.setItem('qid_exp', (new Date(Date.now() + 5000)).toUTCString()); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], @@ -1813,7 +1900,8 @@ describe('User ID', function () { ['admixerId', 'admixerId', 'cookie'], ['amxId', 'amxId', 'html5'], ['deepintentId', 'deepintentId', 'cookie'], - ['kpuid', 'kpuid', 'cookie'])); + ['kpuid', 'kpuid', 'cookie'], + ['qid', 'qid', 'html5'])); requestBidsHook(function () { adUnits.forEach(unit => { @@ -1871,7 +1959,10 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.kpuid'); expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - expect(bid.userIdAsEids.length).to.equal(17); + expect(bid).to.have.deep.nested.property('userId.qid'); + expect(bid.userId.qid).to.equal('testqid'); + + expect(bid.userIdAsEids.length).to.equal(18); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); @@ -1892,6 +1983,8 @@ describe('User ID', function () { coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); localStorage.removeItem('amxId'); localStorage.removeItem('amxId_exp'); + localStorage.removeItem('qid'); + localStorage.removeItem('qid_exp'); done(); }, {adUnits}); }); @@ -2081,8 +2174,10 @@ describe('User ID', function () { localStorage.setItem('amxId', 'test_amxid_id'); localStorage.setItem('amxId_exp', new Date(Date.now() + 5000).toUTCString()) coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); + localStorage.setItem('qid', 'testqid'); + localStorage.setItem('qid_exp', new Date(Date.now() + 5000).toUTCString()) - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, haloIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, akamaiDAPIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); init(config); config.setConfig({ @@ -2120,6 +2215,8 @@ describe('User ID', function () { name: 'amxId', storage: {name: 'amxId', type: 'html5'} }, { name: 'kpuid', storage: {name: 'kpuid', type: 'cookie'} + }, { + name: 'qid', storage: {name: 'qid', type: 'html5'} }] } }); @@ -2191,7 +2288,10 @@ describe('User ID', function () { expect(bid).to.have.deep.nested.property('userId.kpuid'); expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - expect(bid.userIdAsEids.length).to.equal(15); + + expect(bid).to.have.deep.nested.property('userId.qid'); + expect(bid.userId.qid).to.equal('testqid'); + expect(bid.userIdAsEids.length).to.equal(16); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 37452914e79..52f522bdcfc 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -4,6 +4,7 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; import { INSTREAM } from '../../../src/video'; const ENDPOINT = `https://d.vidoomy.com/api/rtbserver/prebid/`; +const PIXELS = ['/test.png', '/test2.png?gdpr={{GDPR}}&gdpr_consent={{GDPR_CONSENT}}'] describe('vidoomyBidAdapter', function() { const adapter = newBidder(spec); @@ -101,24 +102,24 @@ describe('vidoomyBidAdapter', function() { }); it('attaches source and version to endpoint URL as query params', function () { - expect(request[0].url).to.include(ENDPOINT); - expect(request[1].url).to.include(ENDPOINT); + expect(request[0].url).to.equal(ENDPOINT); + expect(request[1].url).to.equal(ENDPOINT); }); it('only accepts first width and height sizes', function () { - expect(request[0].url).to.include('w=300'); - expect(request[0].url).to.include('h=250'); - expect(request[0].url).to.not.include('w=200'); - expect(request[0].url).to.not.include('h=100'); - expect(request[1].url).to.include('w=400'); - expect(request[1].url).to.include('h=225'); + expect('' + request[0].data.w).to.equal('300'); + expect('' + request[0].data.h).to.equal('250'); + expect('' + request[0].data.w).to.not.equal('200'); + expect('' + request[0].data.h).to.not.equal('100'); + expect('' + request[1].data.w).to.equal('400'); + expect('' + request[1].data.h).to.equal('225'); }); it('should send id and pid parameters', function () { - expect(request[0].url).to.include('id=123123'); - expect(request[0].url).to.include('pid=123123'); - expect(request[1].url).to.include('id=456456'); - expect(request[1].url).to.include('pid=456456'); + expect('' + request[0].data.id).to.equal('123123'); + expect('' + request[0].data.pid).to.equal('123123'); + expect('' + request[1].data.id).to.equal('456456'); + expect('' + request[1].data.pid).to.equal('456456'); }); }); @@ -182,7 +183,8 @@ describe('vidoomyBidAdapter', function() { 'networkName': null, 'primaryCatId': 'IAB3-1', 'secondaryCatIds': null - } + }, + 'pixels': PIXELS } } @@ -206,5 +208,22 @@ describe('vidoomyBidAdapter', function() { expect(result[0].requestId).to.equal(serverResponseBanner.body.requestId); }); + + it('should sync user cookies', function () { + const GDPR_CONSENT = 'GDPR_TEST' + const result = spec.getUserSyncs({ + pixelEnabled: true + }, [serverResponseBanner], { consentString: GDPR_CONSENT, gdprApplies: 1 }, null) + expect(result).to.eql([ + { + type: 'image', + url: PIXELS[0] + }, + { + type: 'image', + url: `/test2.png?gdpr=1&gdpr_consent=${GDPR_CONSENT}` + } + ]) + }); }); }); diff --git a/test/spec/modules/viewability_spec.js b/test/spec/modules/viewability_spec.js new file mode 100644 index 00000000000..ab2753daf53 --- /dev/null +++ b/test/spec/modules/viewability_spec.js @@ -0,0 +1,280 @@ +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import * as utils from 'src/utils.js'; +import * as viewability from 'modules/viewability.js'; + +describe('viewability test', () => { + describe('start measurement', () => { + let sandbox; + let intersectionObserverStub; + let setTimeoutStub; + let observeCalled; + let unobserveCalled; + let ti = 1; + beforeEach(() => { + observeCalled = false; + unobserveCalled = false; + sandbox = sinon.sandbox.create(); + + let fakeIntersectionObserver = (stateChange, options) => { + return { + observe: (element) => { + observeCalled = true; + stateChange([{ isIntersecting: true }]); + }, + unobserve: (element) => { + unobserveCalled = true; + }, + }; + }; + + intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver); + setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => { + callback(); + return ti++; + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should trigger appropriate callbacks', () => { + viewability.startMeasurement('0', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); + + sinon.assert.called(intersectionObserverStub); + sinon.assert.called(setTimeoutStub); + expect(observeCalled).to.equal(true); + expect(unobserveCalled).to.equal(true); + }); + + it('should trigger img tracker', () => { + let triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); + viewability.startMeasurement('1', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(triggerPixelSpy.callCount).to.equal(1); + }); + + it('should trigger js tracker', () => { + let insertHtmlIntoIframeSpy = sandbox.spy(utils, ['insertHtmlIntoIframe']); + viewability.startMeasurement('2', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(insertHtmlIntoIframeSpy.callCount).to.equal(1); + }); + + it('should trigger callback tracker', () => { + let callbackFired = false; + viewability.startMeasurement('3', {}, { method: 'callback', value: () => { callbackFired = true; } }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(callbackFired).to.equal(true); + }); + + it('should check for vid uniqueness', () => { + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + viewability.startMeasurement('4', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(logWarnSpy.callCount).to.equal(0); + + viewability.startMeasurement('4', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(logWarnSpy.callCount).to.equal(1); + expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: must provide an unregistered vid`, '4')).to.equal(true); + }); + + it('should check for valid criteria', () => { + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + viewability.startMeasurement('5', {}, { method: 'js', value: 'http://my.tracker/123.js' }, { timeInView: 1000 }); + expect(logWarnSpy.callCount).to.equal(1); + expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: missing criteria`, { timeInView: 1000 })).to.equal(true); + }); + + it('should check for valid tracker', () => { + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + viewability.startMeasurement('6', {}, { method: 'callback', value: 'string' }, { inViewThreshold: 0.5, timeInView: 1000 }); + expect(logWarnSpy.callCount).to.equal(1); + expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: invalid tracker`, { method: 'callback', value: 'string' })).to.equal(true); + }); + + it('should check if element provided', () => { + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + viewability.startMeasurement('7', undefined, { method: 'js', value: 'http://my.tracker/123.js' }, { timeInView: 1000 }); + expect(logWarnSpy.callCount).to.equal(1); + expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: no html element provided`)).to.equal(true); + }); + }); + + describe('stop measurement', () => { + let sandbox; + let intersectionObserverStub; + let setTimeoutStub; + let clearTimeoutStub; + let observeCalled; + let unobserveCalled; + let stateChangeBackup; + let ti = 1; + beforeEach(() => { + observeCalled = false; + unobserveCalled = false; + sandbox = sinon.sandbox.create(); + + let fakeIntersectionObserver = (stateChange, options) => { + return { + observe: (element) => { + stateChangeBackup = stateChange; + observeCalled = true; + stateChange([{ isIntersecting: true }]); + }, + unobserve: (element) => { + unobserveCalled = true; + }, + }; + }; + + intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver); + setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => { + // skipping the callback + return ti++; + }); + clearTimeoutStub = sandbox.stub(window, 'clearTimeout').callsFake((timeoutId) => { }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should clear the timeout', () => { + viewability.startMeasurement('10', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); + stateChangeBackup([{ isIntersecting: false }]); + sinon.assert.called(intersectionObserverStub); + sinon.assert.called(setTimeoutStub); + sinon.assert.called(clearTimeoutStub); + expect(observeCalled).to.equal(true); + }); + + it('should unobserve', () => { + viewability.startMeasurement('11', {}, { method: 'img', value: 'http://my.tracker/123' }, { inViewThreshold: 0.5, timeInView: 1000 }); + sinon.assert.called(intersectionObserverStub); + sinon.assert.called(setTimeoutStub); + expect(observeCalled).to.equal(true); + expect(unobserveCalled).to.equal(false); + + viewability.stopMeasurement('11'); + expect(unobserveCalled).to.equal(true); + sinon.assert.called(clearTimeoutStub); + }); + + it('should check for vid existence', () => { + let logWarnSpy = sandbox.spy(utils, 'logWarn'); + viewability.stopMeasurement('100'); + expect(logWarnSpy.callCount).to.equal(1); + expect(logWarnSpy.calledWith(`${viewability.MODULE_NAME}: must provide a registered vid`, '100')).to.equal(true); + }); + }); + + describe('handle creative messages', () => { + let sandbox; + let intersectionObserverStub; + let setTimeoutStub; + let observeCalled; + let unobserveCalled; + let ti = 1; + let getElementsByTagStub; + let getElementByIdStub; + + let fakeContentWindow = {}; + beforeEach(() => { + observeCalled = false; + unobserveCalled = false; + sandbox = sinon.sandbox.create(); + + let fakeIntersectionObserver = (stateChange, options) => { + return { + observe: (element) => { + observeCalled = true; + stateChange([{ isIntersecting: true }]); + }, + unobserve: (element) => { + unobserveCalled = true; + }, + }; + }; + + intersectionObserverStub = sandbox.stub(window, 'IntersectionObserver').callsFake(fakeIntersectionObserver); + setTimeoutStub = sandbox.stub(window, 'setTimeout').callsFake((callback, timeout) => { + callback(); + return ti++; + }); + + getElementsByTagStub = sandbox.stub(document, 'getElementsByTagName').callsFake((tagName) => { + return [{ + contentWindow: fakeContentWindow, + }]; + }); + getElementByIdStub = sandbox.stub(document, 'getElementById').callsFake((id) => { + return {}; + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should find element by contentWindow', () => { + let viewabilityRecord = { + vid: 1000, + tracker: { + value: 'http://my.tracker/123', + method: 'img', + }, + criteria: { inViewThreshold: 0.5, timeInView: 1000 }, + message: 'Prebid Viewability', + action: 'startMeasurement', + }; + let data = JSON.stringify(viewabilityRecord); + + viewability.receiveMessage({ + data: data, + source: fakeContentWindow, + }); + + sinon.assert.called(getElementsByTagStub); + sinon.assert.called(intersectionObserverStub); + sinon.assert.called(setTimeoutStub); + expect(observeCalled).to.equal(true); + expect(unobserveCalled).to.equal(true); + }); + + it('should find element by id', () => { + let viewabilityRecord = { + vid: 1001, + tracker: { + value: 'http://my.tracker/123', + method: 'img', + }, + criteria: { inViewThreshold: 0.5, timeInView: 1000 }, + message: 'Prebid Viewability', + action: 'startMeasurement', + elementId: '1', + }; + let data = JSON.stringify(viewabilityRecord); + viewability.receiveMessage({ + data: data, + }); + + sinon.assert.called(getElementByIdStub); + sinon.assert.called(intersectionObserverStub); + sinon.assert.called(setTimeoutStub); + expect(observeCalled).to.equal(true); + expect(unobserveCalled).to.equal(true); + }); + + it('should stop measurement', () => { + let viewabilityRecord = { + vid: 1001, + message: 'Prebid Viewability', + action: 'stopMeasurement', + }; + let data = JSON.stringify(viewabilityRecord); + viewability.receiveMessage({ + data: data, + }); + + expect(unobserveCalled).to.equal(true); + }); + }); +}); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index c3d2d216586..d29eb6c25dc 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/visxBidAdapter.js'; import { config } from 'src/config.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from 'src/utils.js'; +import { makeSlot } from '../integration/faker/googletag.js'; describe('VisxAdapter', function () { const adapter = newBidder(spec); @@ -145,17 +146,17 @@ describe('VisxAdapter', function () { const expectedFullImps = [{ 'id': '30b31c1838de1e', 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}]}, - 'ext': {'bidder': {'uid': 903535}} + 'ext': {'bidder': {'uid': 903535, 'adslotExists': false}} }, { 'id': '3150ccb55da321', 'banner': {'format': [{'w': 728, 'h': 90}, {'w': 300, 'h': 250}]}, - 'ext': {'bidder': {'uid': 903535}} + 'ext': {'bidder': {'uid': 903535, 'adslotExists': false}} }, { 'id': '42dbe3a7168a6a', 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}]}, - 'ext': {'bidder': {'uid': 903536}} + 'ext': {'bidder': {'uid': 903536, 'adslotExists': false}} }, { 'id': '39a4e3a7168a6a', @@ -452,7 +453,122 @@ describe('VisxAdapter', function () { 'imp': [{ 'id': '39aff3a7169a6a', 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}]}, - 'ext': {'bidder': {'uid': 903538}} + 'ext': {'bidder': {'uid': 903538, 'adslotExists': false}} + }], + 'tmax': 3000, + 'cur': ['EUR'], + 'source': { + 'ext': { + 'wrapperType': 'Prebid_js', + 'wrapperVersion': '$prebid.version$' + } + }, + 'site': {'page': referrer} + }); + }); + }); + + describe('buildRequests (check ad slot exists)', function () { + function parseRequest(url) { + const res = {}; + (url.split('?')[1] || '').split('&').forEach((it) => { + const couple = it.split('='); + res[couple[0]] = decodeURIComponent(couple[1]); + }); + return res; + } + const bidderRequest = { + timeout: 3000, + refererInfo: { + referer: 'https://example.com' + } + }; + const referrer = bidderRequest.refererInfo.referer; + const bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': 903535 + }, + 'adUnitCode': 'visx-adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + }, + { + 'bidder': 'visx', + 'params': { + 'uid': 903535 + }, + 'adUnitCode': 'visx-adunit-code-2', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + let sandbox; + let documentStub; + + before(function() { + sandbox = sinon.sandbox.create(); + documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs('visx-adunit-code-1').returns({ + id: 'visx-adunit-code-1' + }); + documentStub.withArgs('visx-adunit-element-2').returns({ + id: 'visx-adunit-element-2' + }); + }); + + after(function() { + sandbox.restore(); + }); + + it('should find ad slot by ad unit code as element id', function () { + const request = spec.buildRequests([bidRequests[0]], bidderRequest); + const payload = parseRequest(request.url); + expect(payload).to.be.an('object'); + expect(payload).to.have.property('auids', '903535'); + + const postData = request.data; + expect(postData).to.be.an('object'); + expect(postData).to.deep.equal({ + 'id': '22edbae2733bf6', + 'imp': [{ + 'id': '30b31c1838de1e', + 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}]}, + 'ext': {'bidder': {'uid': 903535, 'adslotExists': true}} + }], + 'tmax': 3000, + 'cur': ['EUR'], + 'source': { + 'ext': { + 'wrapperType': 'Prebid_js', + 'wrapperVersion': '$prebid.version$' + } + }, + 'site': {'page': referrer} + }); + }); + + it('should find ad slot by ad unit code as adUnitPath', function () { + makeSlot({code: 'visx-adunit-code-2', divId: 'visx-adunit-element-2'}); + + const request = spec.buildRequests([bidRequests[1]], bidderRequest); + const payload = parseRequest(request.url); + expect(payload).to.be.an('object'); + expect(payload).to.have.property('auids', '903535'); + + const postData = request.data; + expect(postData).to.be.an('object'); + expect(postData).to.deep.equal({ + 'id': '22edbae2733bf6', + 'imp': [{ + 'id': '30b31c1838de1e', + 'banner': {'format': [{'w': 300, 'h': 250}, {'w': 300, 'h': 600}]}, + 'ext': {'bidder': {'uid': 903535, 'adslotExists': true}} }], 'tmax': 3000, 'cur': ['EUR'], @@ -1149,7 +1265,7 @@ describe('VisxAdapter', function () { it('onTimeout', function () { const data = { timeout: 3000, bidId: '23423', params: { uid: 1 } }; spec.onTimeout(data); - expect(utils.triggerPixel.calledOnceWith('https://t.visx.net/track/bid_timeout?data=' + JSON.stringify(data))).to.equal(true); + expect(utils.triggerPixel.calledOnceWith('https://t.visx.net/track/bid_timeout//' + JSON.stringify(data))).to.equal(true); }); }); diff --git a/test/spec/modules/weboramaRtdProvider_spec.js b/test/spec/modules/weboramaRtdProvider_spec.js index 155f26990a7..081a6f06876 100644 --- a/test/spec/modules/weboramaRtdProvider_spec.js +++ b/test/spec/modules/weboramaRtdProvider_spec.js @@ -1,14 +1,21 @@ -import { setBigseaContextualProfile, weboramaSubmodule } from 'modules/weboramaRtdProvider.js'; -import { server } from 'test/mocks/xhr.js'; -import {config} from 'src/config.js'; - -const responseHeader = {'Content-Type': 'application/json'}; - -// TODO fix it +import { + weboramaSubmodule +} from 'modules/weboramaRtdProvider.js'; +import { + server +} from 'test/mocks/xhr.js'; +import { + storage, + DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY +} from '../../../modules/weboramaRtdProvider.js'; + +const responseHeader = { + 'Content-Type': 'application/json' +}; describe('weboramaRtdProvider', function() { describe('weboramaSubmodule', function() { - it('successfully instantiates and call contextual api', function () { + it('successfully instantiates and call contextual api', function() { const moduleConfig = { params: { weboCtxConf: { @@ -18,271 +25,458 @@ describe('weboramaRtdProvider', function() { } }; - expect(weboramaSubmodule.init(moduleConfig)).to.equal(true); - - let request = server.requests[0]; - - expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); - expect(request.method).to.equal('GET') + expect(weboramaSubmodule.init(moduleConfig)).to.equal(true); }); - it('instantiate without token should fail', function () { + + it('instantiate without contextual token should fail', function() { const moduleConfig = { params: { weboCtxConf: {} } }; - expect(weboramaSubmodule.init(moduleConfig)).to.equal(false); + expect(weboramaSubmodule.init(moduleConfig)).to.equal(false); + }); + + it('instantiate with empty weboUserData conf should return true', function() { + const moduleConfig = { + params: { + weboUserDataConf: {} + } + }; + expect(weboramaSubmodule.init(moduleConfig)).to.equal(true); }); }); - describe('Add Contextual Data', function() { + describe('Handle Set Targeting', function() { + let sandbox; + beforeEach(function() { - let conf = { - site: { - ext: { - data: { - inventory: ['value1'] + sandbox = sinon.sandbox.create(); + + storage.removeDataFromLocalStorage('webo_wam2gam_entry'); + }); + + afterEach(function() { + sandbox.restore(); + }); + + describe('Add Contextual Data', function() { + it('should set gam targeting and send to bidders by default', function() { + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', } } - }, - user: { - ext: { - data: { - visitor: ['value2'] + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_ctx=foo;webo_ctx=bar;webo_ds=baz'); + }); + + it('should set gam targeting but not send to bidders with setPrebidTargeting=true/sendToBidders=false', function() { + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + setPrebidTargeting: true, + sendToBidders: false, } } - }, - cur: ['USD'] - }; + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + let request = server.requests[0]; + + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; + + request.respond(200, responseHeader, JSON.stringify(data)); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); + }); - config.setConfig({ortb2: conf}); - }); - it('should set targeting and ortb2 if omit setTargeting', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setOrtb2: true, + it('should not set gam targeting with setPrebidTargeting=false but send to bidders', function() { + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + setPrebidTargeting: false, + } } + }; + const data = { + webo_ctx: ['foo', 'bar'], + webo_ds: ['baz'], + }; + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }] + }] } - }; - const data = { - webo_ctx: ['foo', 'bar'], - webo_ds: ['baz'], - }; - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); + const onDoneSpy = sinon.spy(); - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + let request = server.requests[0]; - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, - }); + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; - const ortb2 = config.getConfig('ortb2'); + request.respond(200, responseHeader, JSON.stringify(data)); - expect(ortb2.site.ext.data.webo_ctx).to.deep.equal(data.webo_ctx); - expect(ortb2.site.ext.data.webo_ds).to.deep.equal(data.webo_ds); - }); - - it('should set targeting and ortb2 with setTargeting=true', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: true, - setOrtb2: true, - } - } - }; - const data = { - webo_ctx: ['foo', 'bar'], - webo_ds: ['baz'], - }; - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); + expect(onDoneSpy.calledOnce).to.be.true; - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + expect(targeting).to.deep.equal({}); - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar;webo_ctx=foo;webo_ctx=bar;webo_ds=baz'); }); - const ortb2 = config.getConfig('ortb2'); - - expect(ortb2.site.ext.data.webo_ctx).to.deep.equal(data.webo_ctx); - expect(ortb2.site.ext.data.webo_ds).to.deep.equal(data.webo_ds); - }); - it('should set targeting and ortb2 only webo_ctx with setTargeting=true', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: true, - setOrtb2: true, + it('should use default profile in case of api error', function() { + const defaultProfile = { + webo_ctx: ['baz'], + }; + const moduleConfig = { + params: { + weboCtxConf: { + token: 'foo', + targetURL: 'https://prebid.org', + setPrebidTargeting: true, + defaultProfile: defaultProfile, + } } - } - }; - const data = { - webo_ctx: ['foo', 'bar'], - }; + }; - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver' + }] + }] + }; + const onDoneSpy = sinon.spy(); - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + let request = server.requests[0]; - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, - }); + expect(request.method).to.equal('GET'); + expect(request.url).to.equal('https://ctx.weborama.com/api/profile?token=foo&url=https%3A%2F%2Fprebid.org&'); + expect(request.withCredentials).to.be.false; - const ortb2 = config.getConfig('ortb2'); + request.respond(500, responseHeader); - expect(ortb2.site.ext.data.webo_ctx).to.deep.equal(data.webo_ctx); - expect(ortb2.site.ext.data).to.not.have.property('webo_ds'); - }); - it('should set only targeting and not ortb2 with setTargeting=true and setOrtb2=false', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: true, - setOrtb2: false, - } - } - }; - const data = { - webo_ctx: ['foo', 'bar'], - }; + expect(onDoneSpy.calledOnce).to.be.true; - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + 'adunit2': defaultProfile, + }); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_ctx=baz'); + }); + }); - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, + describe('Add WAM2GAM Data', function() { + it('should set gam targeting from local storage and send to bidders by default', function() { + const moduleConfig = { + params: { + weboUserDataConf: {} + } + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_cs=foo;webo_cs=bar;webo_audiences=baz'); }); - const ortb2 = config.getConfig('ortb2'); + it('should set gam targeting but not send to bidders with setPrebidTargeting=true/sendToBidders=false', function() { + const moduleConfig = { + params: { + weboUserDataConf: { + setPrebidTargeting: true, + sendToBidders: false + } + } + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': data, + 'adunit2': data, + }); + + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar'); + }); - expect(ortb2.site.ext.data).to.not.have.property('webo_ctx'); - expect(ortb2.site.ext.data).to.not.have.property('webo_ds'); - }); - it('should set only targeting and not ortb2 with setTargeting=true and omit setOrtb2', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: true, + it('should not set gam targeting with setPrebidTargeting=false but send to bidders', function() { + const moduleConfig = { + params: { + weboUserDataConf: { + setPrebidTargeting: false, + } } - } - }; - const data = { - webo_ctx: ['foo', 'bar'], - }; + }; + const data = { + webo_cs: ['foo', 'bar'], + webo_audiences: ['baz'], + }; + + const entry = { + targeting: data, + }; + + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(entry)); + + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver', + params: { + target: 'foo=bar' + } + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + + expect(targeting).to.deep.equal({}); + + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('foo=bar;webo_cs=foo;webo_cs=bar;webo_audiences=baz'); + }); - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); + it('should use default profile in case of nothing on local storage', function() { + const defaultProfile = { + webo_audiences: ['baz'] + }; + const moduleConfig = { + params: { + weboUserDataConf: { + setPrebidTargeting: true, + defaultProfile: defaultProfile, + } + } + }; - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver' + }] + }] + }; + const onDoneSpy = sinon.spy(); - expect(targeting).to.deep.equal({ - 'adunit1': data, - 'adunit2': data, - }); + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); - const ortb2 = config.getConfig('ortb2'); + expect(onDoneSpy.calledOnce).to.be.true; - expect(ortb2.site.ext.data).to.not.have.property('webo_ctx'); - expect(ortb2.site.ext.data).to.not.have.property('webo_ds'); - }); + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); - it('should set only ortb2 with setTargeting=false', function() { - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: false, - setOrtb2: true, - } - } - }; - const data = { - webo_ctx: ['foo', 'bar'], - }; - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + 'adunit2': defaultProfile, + }); - let request = server.requests[0]; - request.respond(200, responseHeader, JSON.stringify(data)); + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_audiences=baz'); + }); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + it('should use default profile if cant read from local storage', function() { + const defaultProfile = { + webo_audiences: ['baz'] + }; + const moduleConfig = { + params: { + weboUserDataConf: { + setPrebidTargeting: true, + defaultProfile: defaultProfile, + } + } + }; - expect(targeting).to.deep.equal({}); + sandbox.stub(storage, 'localStorageIsEnabled').returns(false); - const ortb2 = config.getConfig('ortb2'); + const adUnitsCodes = ['adunit1', 'adunit2']; + const reqBidsConfigObj = { + adUnits: [{ + bids: [{ + bidder: 'smartadserver' + }] + }] + }; + const onDoneSpy = sinon.spy(); - expect(ortb2.site.ext.data.webo_ctx).to.deep.equal(data.webo_ctx); - expect(ortb2.site.ext.data).to.not.have.property('webo_ds'); - }); - it('should use default profile in case of api error', function() { - const defaultProfile = { - webo_ctx: ['baz'], - }; - const moduleConfig = { - params: { - weboCtxConf: { - token: 'foo', - targetURL: 'https://prebid.org', - setTargeting: true, - defaultProfile: defaultProfile, - } - } - }; + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); - const adUnitsCodes = ['adunit1', 'adunit2']; - weboramaSubmodule.init(moduleConfig); + expect(onDoneSpy.calledOnce).to.be.true; - let request = server.requests[0]; - request.respond(500, responseHeader); + const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); - const targeting = weboramaSubmodule.getTargetingData(adUnitsCodes, moduleConfig); + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + 'adunit2': defaultProfile, + }); - expect(targeting).to.deep.equal({ - 'adunit1': defaultProfile, - 'adunit2': defaultProfile, + expect(reqBidsConfigObj.adUnits[0].bids[0].params.target).to.equal('webo_audiences=baz'); }); - - const ortb2 = config.getConfig('ortb2'); - - expect(ortb2.site.ext.data).to.not.have.property('webo_ctx'); - expect(ortb2.site.ext.data).to.not.have.property('webo_ds'); }); }); }); diff --git a/test/spec/modules/welectBidAdapter_spec.js b/test/spec/modules/welectBidAdapter_spec.js new file mode 100644 index 00000000000..2f2af35eaec --- /dev/null +++ b/test/spec/modules/welectBidAdapter_spec.js @@ -0,0 +1,211 @@ +import { expect } from 'chai'; +import { spec as adapter } from 'modules/welectBidAdapter.js'; + +describe('WelectAdapter', function () { + describe('Check methods existance', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + }); + + describe('Check method isBidRequestValid return', function () { + let bid = { + bidder: 'welect', + params: { + placementId: 'exampleAlias', + domain: 'www.welect.de' + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + }; + let bid2 = { + bidder: 'welect', + params: { + domain: 'www.welect.de' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360] + } + }, + }; + + it('should be true', function () { + expect(adapter.isBidRequestValid(bid)).to.be.true; + }); + + it('should be false because the placementId is missing', function () { + expect(adapter.isBidRequestValid(bid2)).to.be.false; + }); + }); + + describe('Check buildRequests method', function () { + // Bids to be formatted + let bid1 = { + bidder: 'welect', + params: { + placementId: 'exampleAlias' + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bidId: 'abdc' + }; + let bid2 = { + bidder: 'welect', + params: { + placementId: 'exampleAlias', + domain: 'www.welect2.de' + }, + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bidId: 'abdc', + gdprConsent: { + gdprApplies: 1, + gdprConsent: 'some_string' + } + }; + + let data1 = { + bid_id: 'abdc', + width: 640, + height: 360 + } + + let data2 = { + bid_id: 'abdc', + width: 640, + height: 360, + gdpr_consent: { + gdprApplies: 1, + tcString: 'some_string' + } + } + + // Formatted requets + let request1 = { + method: 'POST', + url: 'https://www.welect.de/api/v2/preflight/exampleAlias', + data: data1, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + } + }; + + let request2 = { + method: 'POST', + url: 'https://www.welect2.de/api/v2/preflight/exampleAlias', + data: data2, + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + } + } + + it('defaults to www.welect.de, without gdpr object', function () { + expect(adapter.buildRequests([bid1])).to.deep.equal([request1]); + }) + + it('must return the right formatted requests, with gdpr object', function () { + expect(adapter.buildRequests([bid2])).to.deep.equal([request2]); + }); + }); + + describe('Check interpretResponse method return', function () { + // invalid server response + let unavailableResponse = { + body: { + available: false + } + }; + + let availableResponse = { + body: { + available: true, + bidResponse: { + ad: { + video: 'some vast url' + }, + meta: { + advertiserDomains: [], + }, + cpm: 17, + creativeId: 'svmpreview', + currency: 'EUR', + netRevenue: true, + requestId: 'some bid id', + ttl: 120, + vastUrl: 'some vast url', + height: 640, + width: 320 + } + } + } + // bid Request + let bid = { + data: { + bid_id: 'some bid id', + width: 640, + height: 320, + gdpr_consent: { + gdprApplies: 1, + tcString: 'some_string' + } + }, + method: 'POST', + url: 'https://www.welect.de/api/v2/preflight/exampleAlias', + options: { + contentType: 'application/json', + withCredentials: false, + crossOrigin: true, + } + }; + // Formatted reponse + let result = { + ad: { + video: 'some vast url' + }, + meta: { + advertiserDomains: [] + }, + cpm: 17, + creativeId: 'svmpreview', + currency: 'EUR', + height: 640, + netRevenue: true, + requestId: 'some bid id', + ttl: 120, + vastUrl: 'some vast url', + width: 320 + } + + it('if response reflects unavailability, should be empty', function () { + expect(adapter.interpretResponse(unavailableResponse, bid)).to.deep.equal([]); + }); + + it('if response reflects availability, should equal result', function () { + expect(adapter.interpretResponse(availableResponse, bid)).to.deep.equal([result]) + }) + }); +}); diff --git a/test/spec/modules/yahoosspBidAdapter_spec.js b/test/spec/modules/yahoosspBidAdapter_spec.js index 5eb82b399cc..e0af8784605 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahoosspBidAdapter_spec.js @@ -11,7 +11,7 @@ const DEFAULT_AD_UNIT_CODE = '/19968336/header-bid-tag-1'; const DEFAULT_AD_UNIT_TYPE = 'banner'; const DEFAULT_PARAMS_BID_OVERRIDE = {}; const DEFAULT_VIDEO_CONTEXT = 'instream'; -const ADAPTER_VERSION = '1.0.1'; +const ADAPTER_VERSION = '1.0.2'; const PREBID_VERSION = '$prebid.version$'; const INTEGRATION_METHOD = 'prebid.js'; @@ -656,6 +656,33 @@ describe('YahooSSP Bid Adapter:', () => { const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; expect(data.imp[0].ext.data).to.deep.equal(validBidRequests[0].ortb2Imp.ext.data); }); + // adUnit.ortb2Imp.instl + it(`should allow adUnit.ortb2Imp.instl numeric boolean "1" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: 1 + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.deep.equal(validBidRequests[0].ortb2Imp.instl); + }); + + it(`should prevent adUnit.ortb2Imp.instl boolean "true" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: true + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.not.exist; + }); + + it(`should prevent adUnit.ortb2Imp.instl boolean "false" to be added to the bid-request`, () => { + let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) + validBidRequests[0].ortb2Imp = { + instl: false + }; + const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + expect(data.imp[0].instl).to.not.exist; + }); }); describe('e2etest mode support:', () => { diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 698bfb92888..f80cad46d50 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -73,6 +73,12 @@ const VIDEO_REQUEST = Object.assign({}, REQUEST, { } }) +const NATIVE_REQUEST = Object.assign({}, REQUEST, { + 'mediaTypes': { + 'native': { } + } +}) + const RESPONSE = { advertiser: 'yieldlab', curl: 'https://www.yieldlab.de', @@ -84,6 +90,42 @@ const RESPONSE = { adtype: 'BANNER' } +const NATIVE_RESPONSE = Object.assign({}, RESPONSE, { + 'adtype': 'NATIVE', + 'native': { + 'link': { + 'url': 'https://www.yieldlab.de' + }, + 'assets': [ + { + 'id': 1, + 'title': { + 'text': 'This is a great headline' + } + }, + { + 'id': 2, + 'img': { + 'url': 'https://localhost:8080/yl-logo100x100.jpg', + 'w': 100, + 'h': 100 + } + }, + { + 'id': 3, + 'data': { + 'value': 'Native body value' + } + } + ], + 'imptrackers': [ + 'http://localhost:8080/ve?d=ODE9ZSY2MTI1MjAzNjMzMzYxPXN0JjA0NWUwZDk0NTY5Yi05M2FiLWUwZTQtOWFjNy1hYWY0MzFiZj1kaXQmMj12', + 'http://localhost:8080/md/1111/9efa4e76-2030-4f04-bb9f-322541f8d611?mdata=false&pvid=false&ids=x:1', + 'http://localhost:8080/imp?s=13216&d=2171514&a=12548955&ts=1633363025216&tid=fb134faa-7ca9-4e0e-ba39-b96549d0e540&l=0' + ] + } +}) + const VIDEO_RESPONSE = Object.assign({}, RESPONSE, { 'adtype': 'VIDEO' }) @@ -297,6 +339,45 @@ describe('yieldlabBidAdapter', function () { expect(result[0].vastUrl).to.include('&id=abc') }) + it('should add adUrl and native assets when type is Native', function () { + const result = spec.interpretResponse({body: [NATIVE_RESPONSE]}, {validBidRequests: [NATIVE_REQUEST], queryParams: REQPARAMS}) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(0.01) + expect(result[0].mediaType).to.equal('native') + expect(result[0].adUrl).to.include('https://ad.yieldlab.net/d/1111/2222/?ts=') + expect(result[0].native.title).to.equal('This is a great headline') + expect(result[0].native.body).to.equal('Native body value') + expect(result[0].native.image.url).to.equal('https://localhost:8080/yl-logo100x100.jpg') + expect(result[0].native.image.width).to.equal(100) + expect(result[0].native.image.height).to.equal(100) + expect(result[0].native.clickUrl).to.equal('https://www.yieldlab.de') + expect(result[0].native.impressionTrackers.length).to.equal(3) + }) + + it('should add adUrl and default native assets when type is Native', function () { + const NATIVE_RESPONSE_2 = Object.assign({}, NATIVE_RESPONSE, { + 'native': { + 'link': { + 'url': 'https://www.yieldlab.de' + }, + 'assets': [], + 'imptrackers': [] + } + }) + const result = spec.interpretResponse({body: [NATIVE_RESPONSE_2]}, {validBidRequests: [NATIVE_REQUEST], queryParams: REQPARAMS}) + + expect(result[0].requestId).to.equal('2d925f27f5079f') + expect(result[0].cpm).to.equal(0.01) + expect(result[0].mediaType).to.equal('native') + expect(result[0].adUrl).to.include('https://ad.yieldlab.net/d/1111/2222/?ts=') + expect(result[0].native.title).to.equal('') + expect(result[0].native.body).to.equal('') + expect(result[0].native.image.url).to.equal('') + expect(result[0].native.image.width).to.equal(0) + expect(result[0].native.image.height).to.equal(0) + }) + it('should append gdpr parameters to vastUrl', function () { const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST], queryParams: REQPARAMS_GDPR}) diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index 4186c5da41a..1b38bb94a8c 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -7,6 +7,8 @@ const ENDPOINT = 'https://y.one.impact-ad.jp/h_bid'; const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync'; const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/dac-video-prebid.min.js'; +const DEFAULT_VIDEO_SIZE = {w: 640, h: 360}; + describe('yieldoneBidAdapter', function() { const adapter = newBidder(spec); @@ -40,32 +42,7 @@ describe('yieldoneBidAdapter', function() { }); describe('buildRequests', function () { - let bidRequests = [ - { - 'bidder': 'yieldone', - 'params': { - placementId: '36891' - }, - 'adUnitCode': 'adunit-code1', - 'sizes': [[300, 250], [336, 280]], - 'bidId': '23beaa6af6cdde', - 'bidderRequestId': '19c0c1efdf37e7', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - }, - { - 'bidder': 'yieldone', - 'params': { - placementId: '47919' - }, - 'adUnitCode': 'adunit-code2', - 'sizes': [[300, 250]], - 'bidId': '382091349b149f"', - 'bidderRequestId': '"1f9c98192de251"', - 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - } - ]; - - let bidderRequest = { + const bidderRequest = { refererInfo: { numIframes: 0, reachedTop: true, @@ -74,49 +51,318 @@ describe('yieldoneBidAdapter', function() { } }; - const request = spec.buildRequests(bidRequests, bidderRequest); + describe('Basic', function () { + const bidRequests = [ + { + 'bidder': 'yieldone', + 'params': {placementId: '36891'}, + 'adUnitCode': 'adunit-code1', + 'bidId': '23beaa6af6cdde', + 'bidderRequestId': '19c0c1efdf37e7', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + }, + { + 'bidder': 'yieldone', + 'params': {placementId: '47919'}, + 'adUnitCode': 'adunit-code2', + 'bidId': '382091349b149f"', + 'bidderRequestId': '"1f9c98192de251"', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + } + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); - it('sends bid request to our endpoint via GET', function () { - expect(request[0].method).to.equal('GET'); - expect(request[1].method).to.equal('GET'); + it('sends bid request to our endpoint via GET', function () { + expect(request[0].method).to.equal('GET'); + expect(request[1].method).to.equal('GET'); + }); + it('attaches source and version to endpoint URL as query params', function () { + expect(request[0].url).to.equal(ENDPOINT); + expect(request[1].url).to.equal(ENDPOINT); + }); + it('adUnitCode should be sent as uc parameters on any requests', function () { + expect(request[0].data.uc).to.equal('adunit-code1'); + expect(request[1].data.uc).to.equal('adunit-code2'); + }); }); - it('attaches source and version to endpoint URL as query params', function () { - expect(request[0].url).to.equal(ENDPOINT); - expect(request[1].url).to.equal(ENDPOINT); - }); + describe('Old Format', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + mediaType: 'banner', + sizes: [[300, 250], [336, 280]], + }, + { + params: {placementId: '1'}, + mediaType: 'banner', + sizes: [[336, 280]], + }, + { + // It doesn't actually exist. + params: {placementId: '2'}, + }, + { + params: {placementId: '3'}, + mediaType: 'video', + sizes: [[1280, 720], [1920, 1080]], + }, + { + params: {placementId: '4'}, + mediaType: 'video', + sizes: [[1920, 1080]], + }, + { + params: {placementId: '5'}, + mediaType: 'video', + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); - it('parameter sz has more than one size on banner requests', function () { - expect(request[0].data.sz).to.equal('300x250,336x280'); - expect(request[1].data.sz).to.equal('300x250'); + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data.sz).to.equal('336x280'); + expect(request[2].data.sz).to.equal(''); + expect(request[3].data).to.not.have.property('sz'); + expect(request[4].data).to.not.have.property('sz'); + expect(request[5].data).to.not.have.property('sz'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + expect(request[0].data).to.not.have.property('w'); + expect(request[1].data).to.not.have.property('w'); + expect(request[2].data).to.not.have.property('w'); + expect(request[3].data.w).to.equal(1280); + expect(request[3].data.h).to.equal(720); + expect(request[4].data.w).to.equal(1920); + expect(request[4].data.h).to.equal(1080); + expect(request[5].data.w).to.equal(DEFAULT_VIDEO_SIZE.w); + expect(request[5].data.h).to.equal(DEFAULT_VIDEO_SIZE.h); + }); }); - it('width and height should be set as separate parameters on outstream requests', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = {context: 'outstream'}; - const request = spec.buildRequests([bidRequest], bidderRequest); - expect(request[0].data.w).to.equal('300'); - expect(request[0].data.h).to.equal('250'); + describe('New Format', function () { + const bidRequests = [ + { + params: {placementId: '0'}, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + }, + }, + { + params: {placementId: '1'}, + mediaTypes: { + banner: { + sizes: [[336, 280]], + }, + }, + }, + { + // It doesn't actually exist. + params: {placementId: '2'}, + mediaTypes: { + banner: { + }, + }, + }, + { + params: {placementId: '3'}, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[1280, 720], [1920, 1080]], + }, + }, + }, + { + params: {placementId: '4'}, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [1920, 1080], + }, + }, + }, + { + params: {placementId: '5'}, + mediaTypes: { + video: { + context: 'outstream', + }, + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data.sz).to.equal('336x280'); + expect(request[2].data.sz).to.equal(''); + expect(request[3].data).to.not.have.property('sz'); + expect(request[4].data).to.not.have.property('sz'); + expect(request[5].data).to.not.have.property('sz'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + expect(request[0].data).to.not.have.property('w'); + expect(request[1].data).to.not.have.property('w'); + expect(request[2].data).to.not.have.property('w'); + expect(request[3].data.w).to.equal(1280); + expect(request[3].data.h).to.equal(720); + expect(request[4].data.w).to.equal(1920); + expect(request[4].data.h).to.equal(1080); + expect(request[5].data.w).to.equal(DEFAULT_VIDEO_SIZE.w); + expect(request[5].data.h).to.equal(DEFAULT_VIDEO_SIZE.h); + }); }); - it('adUnitCode should be sent as uc parameters on any requests', function () { - expect(request[0].data.uc).to.equal('adunit-code1'); - expect(request[1].data.uc).to.equal('adunit-code2'); + describe('Multiple Format', function () { + const bidRequests = [ + { + // It will be treated as a banner. + params: { + placementId: '0', + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + video: { + context: 'outstream', + playerSize: [1920, 1080], + }, + }, + }, + { + // It will be treated as a video. + params: { + placementId: '1', + playerParams: {}, + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + video: { + context: 'outstream', + playerSize: [1920, 1080], + }, + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data).to.not.have.property('sz'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + expect(request[0].data).to.not.have.property('w'); + expect(request[1].data.w).to.equal(1920); + expect(request[1].data.h).to.equal(1080); + }); }); - describe('userid idl_env should be passed to querystring', function () { - const bid = deepClone([bidRequests[0]]); + describe('FLUX Format', function () { + const bidRequests = [ + { + // It will be treated as a banner. + params: { + placementId: '0', + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + video: { + context: 'outstream', + playerSize: [[1, 1]], + }, + }, + }, + { + // It will be treated as a video. + params: { + placementId: '1', + playerParams: {}, + playerSize: [1920, 1080], + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + video: { + context: 'outstream', + playerSize: [[1, 1]], + }, + }, + }, + { + // It will be treated as a video. + params: { + placementId: '2', + playerParams: {}, + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]], + }, + video: { + context: 'outstream', + playerSize: [[1, 1]], + }, + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + + it('parameter sz has more than one size on banner requests', function () { + expect(request[0].data.sz).to.equal('300x250,336x280'); + expect(request[1].data).to.not.have.property('sz'); + expect(request[2].data).to.not.have.property('sz'); + }); + + it('width and height should be set as separate parameters on outstream requests', function () { + expect(request[0].data).to.not.have.property('w'); + expect(request[1].data.w).to.equal(1920); + expect(request[1].data.h).to.equal(1080); + expect(request[2].data.w).to.equal(DEFAULT_VIDEO_SIZE.w); + expect(request[2].data.h).to.equal(DEFAULT_VIDEO_SIZE.h); + }); + }); + describe('LiveRampID', function () { it('dont send LiveRampID if undefined', function () { - bid[0].userId = {}; - const request = spec.buildRequests(bid, bidderRequest); + const bidRequests = [ + { + params: {placementId: '0'}, + }, + { + params: {placementId: '1'}, + userId: {}, + }, + { + params: {placementId: '2'}, + userId: undefined, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request[0].data).to.not.have.property('lr_env'); + expect(request[1].data).to.not.have.property('lr_env'); + expect(request[2].data).to.not.have.property('lr_env'); }); it('should send LiveRampID if available', function () { - bid[0].userId = {idl_env: 'idl_env_sample'}; - const request = spec.buildRequests(bid, bidderRequest); + const bidRequests = [ + { + params: {placementId: '0'}, + userId: {idl_env: 'idl_env_sample'}, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); expect(request[0].data.lr_env).to.equal('idl_env_sample'); }); }); diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..15a1155f378 --- /dev/null +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -0,0 +1,427 @@ +import zetaAnalyticsAdapter from 'modules/zeta_global_sspAnalyticsAdapter.js'; +import {config} from 'src/config'; +import CONSTANTS from 'src/constants.json'; +import {logError} from '../../../src/utils'; + +let utils = require('src/utils'); +let events = require('src/events'); + +const MOCK = { + STUB: { + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + }, + AUCTION_END: { + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'timestamp': 1638441234544, + 'auctionEnd': 1638441234784, + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': '/19968336/header-bid-tag-0', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'zeta_global_ssp', + 'params': { + 'sid': 111, + 'tags': { + 'shortname': 'prebid_analytics_event_test_shortname', + 'position': 'test_position' + } + } + }, + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13232385 + } + } + ], + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83' + } + ], + 'adUnitCodes': [ + '/19968336/header-bid-tag-0' + ], + 'bidderRequests': [ + { + 'bidderCode': 'zeta_global_ssp', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'bidderRequestId': '1207cb49191887', + 'bids': [ + { + 'bidder': 'zeta_global_ssp', + 'params': { + 'sid': 111, + 'tags': { + 'shortname': 'prebid_analytics_event_test_shortname', + 'position': 'test_position' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '206be9a13236af', + 'bidderRequestId': '1207cb49191887', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1638441234544, + 'timeout': 400, + 'refererInfo': { + 'referer': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html' + ], + 'canonicalUrl': null + }, + 'start': 1638441234547 + }, + { + 'bidderCode': 'appnexus', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'bidderRequestId': '32b97f0a935422', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13232385 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '41badc0e164c758', + 'bidderRequestId': '32b97f0a935422', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1638441234544, + 'timeout': 400, + 'refererInfo': { + 'referer': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html' + ], + 'canonicalUrl': null + }, + 'start': 1638441234550 + } + ], + 'noBids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 13232385 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '41badc0e164c758', + 'bidderRequestId': '32b97f0a935422', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'bidsReceived': [ + { + 'bidderCode': 'zeta_global_ssp', + 'width': 480, + 'height': 320, + 'statusMessage': 'Bid available', + 'adId': '5759bb3ef7be1e8', + 'requestId': '206be9a13236af', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 2.258302852806723, + 'currency': 'USD', + 'ad': 'test_ad', + 'ttl': 200, + 'creativeId': '456456456', + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [ + 'viaplay.fi' + ] + }, + 'originalCpm': 2.258302852806723, + 'originalCurrency': 'USD', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'responseTimestamp': 1638441234670, + 'requestTimestamp': 1638441234547, + 'bidder': 'zeta_global_ssp', + 'adUnitCode': '/19968336/header-bid-tag-0', + 'timeToRespond': 123, + 'pbLg': '2.00', + 'pbMg': '2.20', + 'pbHg': '2.25', + 'pbAg': '2.25', + 'pbDg': '2.25', + 'pbCg': '', + 'size': '480x320', + 'adserverTargeting': { + 'hb_bidder': 'zeta_global_ssp', + 'hb_adid': '5759bb3ef7be1e8', + 'hb_pb': '2.20', + 'hb_size': '480x320', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'viaplay.fi' + } + } + ], + 'winningBids': [], + 'timeout': 400 + }, + AD_RENDER_SUCCEEDED: { + 'doc': { + 'location': { + 'href': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'protocol': 'http:', + 'host': 'localhost:63342', + 'hostname': 'localhost', + 'port': '63342', + 'pathname': '/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'hash': '', + 'origin': 'http://test-zeta-ssp.net:63342', + 'ancestorOrigins': { + '0': 'http://test-zeta-ssp.net:63342' + } + } + }, + 'bid': { + 'bidderCode': 'zeta_global_ssp', + 'width': 480, + 'height': 320, + 'statusMessage': 'Bid available', + 'adId': '5759bb3ef7be1e8', + 'requestId': '206be9a13236af', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 2.258302852806723, + 'currency': 'USD', + 'ad': 'test_ad', + 'ttl': 200, + 'creativeId': '456456456', + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [ + 'viaplay.fi' + ] + }, + 'originalCpm': 2.258302852806723, + 'originalCurrency': 'USD', + 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'responseTimestamp': 1638441234670, + 'requestTimestamp': 1638441234547, + 'bidder': 'zeta_global_ssp', + 'adUnitCode': '/19968336/header-bid-tag-0', + 'timeToRespond': 123, + 'pbLg': '2.00', + 'pbMg': '2.20', + 'pbHg': '2.25', + 'pbAg': '2.25', + 'pbDg': '2.25', + 'pbCg': '', + 'size': '480x320', + 'adserverTargeting': { + 'hb_bidder': 'zeta_global_ssp', + 'hb_adid': '5759bb3ef7be1e8', + 'hb_pb': '2.20', + 'hb_size': '480x320', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'viaplay.fi' + }, + 'status': 'rendered', + 'params': [ + { + 'sid': 111, + 'tags': { + 'shortname': 'prebid_analytics_event_test_shortname', + 'position': 'test_position' + } + } + ] + }, + 'adId': '5759bb3ef7be1e8' + } +} + +describe('Zeta Global SSP Analytics Adapter', function() { + let sandbox; + let xhr; + let requests; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + requests = []; + xhr = sandbox.useFakeXMLHttpRequest(); + xhr.onCreate = request => requests.push(request); + sandbox.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + sandbox.restore(); + config.resetConfig(); + }); + + it('should require publisherId', function () { + sandbox.stub(utils, 'logError'); + zetaAnalyticsAdapter.enableAnalytics({ + options: {} + }); + expect(utils.logError.called).to.equal(true); + }); + + describe('handle events', function() { + beforeEach(function() { + zetaAnalyticsAdapter.enableAnalytics({ + options: { + sid: 111 + } + }); + }); + + afterEach(function () { + zetaAnalyticsAdapter.disableAnalytics(); + }); + + it('events are sent', function() { + this.timeout(5000); + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.NO_BID, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BID_WON, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BIDDER_DONE, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BIDDER_ERROR, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.SET_TARGETING, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BEFORE_BIDDER_HTTP, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.REQUEST_BIDS, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.ADD_AD_UNITS, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.AD_RENDER_FAILED, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED); + events.emit(CONSTANTS.EVENTS.TCF2_ENFORCEMENT, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, MOCK.STUB); + events.emit(CONSTANTS.EVENTS.STALE_RENDER, MOCK.STUB); + + expect(requests.length).to.equal(2); + expect(JSON.parse(requests[0].requestBody)).to.deep.equal(MOCK.AUCTION_END); + expect(JSON.parse(requests[1].requestBody)).to.deep.equal(MOCK.AD_RENDER_SUCCEEDED); + }); + }); +}); diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index dcb0183fb4c..7d4a115958e 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -1,4 +1,5 @@ import {spec} from '../../../modules/zeta_global_sspBidAdapter.js' +import {BANNER, VIDEO} from '../../../src/mediaTypes'; describe('Zeta Ssp Bid Adapter', function () { const eids = [ @@ -143,7 +144,22 @@ describe('Zeta Ssp Bid Adapter', function () { 'https://example2.com' ], h: 150, - w: 200 + w: 200, + ext: { + bidtype: 'video' + } + }, + { + id: 'auctionId3', + impid: 'impId3', + price: 0.2, + adm: '', + crid: 'creativeId3', + adomain: [ + 'https://example3.com' + ], + h: 400, + w: 300 } ] } @@ -159,6 +175,8 @@ describe('Zeta Ssp Bid Adapter', function () { const receivedBid1 = response.body.seatbid[0].bid[0]; expect(bid1).to.not.be.empty; expect(bid1.ad).to.equal(receivedBid1.adm); + expect(bid1.vastXml).to.be.undefined; + expect(bid1.mediaType).to.equal(BANNER); expect(bid1.cpm).to.equal(receivedBid1.price); expect(bid1.height).to.equal(receivedBid1.h); expect(bid1.width).to.equal(receivedBid1.w); @@ -169,11 +187,25 @@ describe('Zeta Ssp Bid Adapter', function () { const receivedBid2 = response.body.seatbid[0].bid[1]; expect(bid2).to.not.be.empty; expect(bid2.ad).to.equal(receivedBid2.adm); + expect(bid2.vastXml).to.equal(receivedBid2.adm); + expect(bid2.mediaType).to.equal(VIDEO); expect(bid2.cpm).to.equal(receivedBid2.price); expect(bid2.height).to.equal(receivedBid2.h); expect(bid2.width).to.equal(receivedBid2.w); expect(bid2.requestId).to.equal(receivedBid2.impid); expect(bid2.meta.advertiserDomains).to.equal(receivedBid2.adomain); + + const bid3 = bidResponse[2]; + const receivedBid3 = response.body.seatbid[0].bid[2]; + expect(bid3).to.not.be.empty; + expect(bid3.ad).to.equal(receivedBid3.adm); + expect(bid3.vastXml).to.equal(receivedBid3.adm); + expect(bid3.mediaType).to.equal(VIDEO); + expect(bid3.cpm).to.equal(receivedBid3.price); + expect(bid3.height).to.equal(receivedBid3.h); + expect(bid3.width).to.equal(receivedBid3.w); + expect(bid3.requestId).to.equal(receivedBid3.impid); + expect(bid3.meta.advertiserDomains).to.equal(receivedBid3.adomain); }); it('Different cases for user syncs', function () { diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index a01c6ad3c2e..f414febcebe 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -162,7 +162,7 @@ describe('adapterManager tests', function () { 'bidderCode': 'appnexus', 'auctionId': '1863e370099523', 'bidderRequestId': '2946b569352ef2', - 'tid': '34566b569352ef2', + 'uniquePbsTid': '34566b569352ef2', 'bids': [ { 'bidder': 'appnexus', @@ -479,7 +479,7 @@ describe('adapterManager tests', function () { 'bidderCode': 'appnexus', 'auctionId': '1863e370099523', 'bidderRequestId': '2946b569352ef2', - 'tid': '34566b569352ef2', + 'uniquePbsTid': '34566b569352ef2', 'timeout': 1000, 'src': 's2s', 'adUnitsS2SCopy': [ @@ -708,7 +708,7 @@ describe('adapterManager tests', function () { 'bidderCode': 'appnexus', 'auctionId': '1863e370099523', 'bidderRequestId': '2946b569352ef2', - 'tid': '34566b569352ef2', + 'uniquePbsTid': '34566b569352ef2', 'timeout': 1000, 'src': 's2s', 'adUnitsS2SCopy': [ @@ -844,7 +844,7 @@ describe('adapterManager tests', function () { 'bidderCode': 'pubmatic', 'auctionId': '1863e370099523', 'bidderRequestId': '2946b569352ef2', - 'tid': '2342342342lfi23', + 'uniquePbsTid': '2342342342lfi23', 'timeout': 1000, 'src': 's2s', 'adUnitsS2SCopy': [ @@ -1041,6 +1041,21 @@ describe('adapterManager tests', function () { sinon.assert.calledTwice(prebidServerAdapterMock.callBids); }); + it('should have one tid for ALL s2s bidRequests', function () { + let adUnits = utils.deepClone(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'pubmatic'], bid.bidder)); + return adUnit; + }) + let bidRequests = adapterManager.makeBidRequests(adUnits, 1111, 2222, 1000); + adapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + const firstBid = prebidServerAdapterMock.callBids.firstCall.args[0]; + const secondBid = prebidServerAdapterMock.callBids.secondCall.args[0]; + + // TIDS should be the same + expect(firstBid.tid).to.equal(secondBid.tid); + }); + it('should fire for simultaneous s2s and client requests', function () { adapterManager.bidderRegistry['adequant'] = adequantAdapterMock; let adUnits = utils.deepClone(getAdUnits()).map(adUnit => { diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 27bb456905d..4eaf414bf85 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -5,6 +5,7 @@ import { createBidReceived } from 'test/fixtures/fixtures.js'; import CONSTANTS from 'src/constants.json'; import { auctionManager } from 'src/auctionManager.js'; import * as utils from 'src/utils.js'; +import {deepClone} from 'src/utils.js'; const bid1 = { 'bidderCode': 'rubicon', @@ -227,8 +228,6 @@ describe('targeting tests', function () { let sandbox; let enableSendAllBids = false; let useBidCache; - let bidCacheFilterFunction; - let undef; beforeEach(function() { sandbox = sinon.sandbox.create(); @@ -243,16 +242,12 @@ describe('targeting tests', function () { if (key === 'useBidCache') { return useBidCache; } - if (key === 'bidCacheFilterFunction') { - return bidCacheFilterFunction; - } return origGetConfig.apply(config, arguments); }); }); afterEach(function () { sandbox.restore(); - bidCacheFilterFunction = undef; }); describe('getAllTargeting', function () { @@ -467,6 +462,50 @@ describe('targeting tests', function () { }); }); + describe('targetingControls.allowZeroCpmBids', function () { + let bid4; + let bidderSettingsStorage; + + before(function() { + bidderSettingsStorage = $$PREBID_GLOBAL$$.bidderSettings; + }); + + beforeEach(function () { + bid4 = utils.deepClone(bid1); + bid4.adserverTargeting = { + hb_pb: '0.0', + hb_adid: '567891011', + hb_bidder: 'appnexus', + }; + bid4.bidder = bid4.bidderCode = 'appnexus'; + bid4.cpm = 0; + bidsReceived = [bid4]; + }); + + after(function() { + bidsReceived = [bid1, bid2, bid3]; + $$PREBID_GLOBAL$$.bidderSettings = bidderSettingsStorage; + }) + + it('targeting should not include a 0 cpm by default', function() { + bid4.adserverTargeting = {}; + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.deep.equal({}); + }); + + it('targeting should allow a 0 cpm with targetingControls.allowZeroCpmBids set to true', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + standard: { + allowZeroCpmBids: true + } + }; + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']).to.include.all.keys('hb_pb', 'hb_bidder', 'hb_adid', 'hb_bidder_appnexus', 'hb_adid_appnexus', 'hb_pb_appnexus'); + expect(targeting['/123456/header-bid-tag-0']['hb_pb']).to.equal('0.0') + }); + }); + describe('targetingControls.allowTargetingKeys', function () { let bid4; @@ -507,6 +546,77 @@ describe('targeting tests', function () { }); }); + describe('targetingControls.addTargetingKeys', function () { + let winningBid = null; + + beforeEach(function () { + bidsReceived = [bid1, bid2, nativeBid1, nativeBid2].map(deepClone); + bidsReceived.forEach((bid) => { + bid.adserverTargeting[CONSTANTS.TARGETING_KEYS.SOURCE] = 'test-source'; + bid.adUnitCode = 'adunit'; + if (winningBid == null || bid.cpm > winningBid.cpm) { + winningBid = bid; + } + }); + enableSendAllBids = true; + }); + + const expandKey = function (key) { + const keys = new Set(); + if (winningBid.adserverTargeting[key] != null) { + keys.add(key); + } + bidsReceived + .filter((bid) => bid.adserverTargeting[key] != null) + .map((bid) => bid.bidderCode) + .forEach((code) => keys.add(`${key}_${code}`.substr(0, 20))); + return new Array(...keys); + } + + const targetingResult = function () { + return targetingInstance.getAllTargeting(['adunit'])['adunit']; + } + + it('should include added keys', function () { + config.setConfig({ + targetingControls: { + addTargetingKeys: ['SOURCE'] + } + }); + expect(targetingResult()).to.include.all.keys(...expandKey(CONSTANTS.TARGETING_KEYS.SOURCE)); + }); + + it('should keep default and native keys', function() { + config.setConfig({ + targetingControls: { + addTargetingKeys: ['SOURCE'] + } + }); + const defaultKeys = new Set(Object.values(CONSTANTS.DEFAULT_TARGETING_KEYS)); + Object.values(CONSTANTS.NATIVE_KEYS).forEach((k) => defaultKeys.add(k)); + + const expectedKeys = new Set(); + bidsReceived + .map((bid) => Object.keys(bid.adserverTargeting)) + .reduce((left, right) => left.concat(right), []) + .filter((key) => defaultKeys.has(key)) + .map(expandKey) + .reduce((left, right) => left.concat(right), []) + .forEach((k) => expectedKeys.add(k)); + expect(targetingResult()).to.include.all.keys(...expectedKeys); + }); + + it('should not be allowed together with allowTargetingKeys', function () { + config.setConfig({ + targetingControls: { + allowTargetingKeys: [CONSTANTS.TARGETING_KEYS.BIDDER], + addTargetingKeys: [CONSTANTS.TARGETING_KEYS.SOURCE] + } + }); + expect(targetingResult).to.throw(); + }); + }); + describe('targetingControls.allowSendAllBidsTargetingKeys', function () { let bid4; @@ -791,93 +901,6 @@ describe('targeting tests', function () { expect(bids[0].adId).to.equal('adid-2'); }); - it('should use bidCacheFilterFunction', function() { - auctionManagerStub.returns([ - createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', mediaType: 'banner'}), - createBidReceived({bidder: 'appnexus', cpm: 5, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-2', mediaType: 'banner'}), - createBidReceived({bidder: 'appnexus', cpm: 6, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-1', adId: 'adid-3', mediaType: 'banner'}), - createBidReceived({bidder: 'appnexus', cpm: 8, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-1', adId: 'adid-4', mediaType: 'banner'}), - createBidReceived({bidder: 'appnexus', cpm: 27, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-2', adId: 'adid-5', mediaType: 'video'}), - createBidReceived({bidder: 'appnexus', cpm: 25, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-2', adId: 'adid-6', mediaType: 'video'}), - createBidReceived({bidder: 'appnexus', cpm: 26, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-3', adId: 'adid-7', mediaType: 'video'}), - createBidReceived({bidder: 'appnexus', cpm: 28, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-3', adId: 'adid-8', mediaType: 'video'}), - ]); - - let adUnitCodes = ['code-0', 'code-1', 'code-2', 'code-3']; - targetingInstance.setLatestAuctionForAdUnit('code-0', 2); - targetingInstance.setLatestAuctionForAdUnit('code-1', 2); - targetingInstance.setLatestAuctionForAdUnit('code-2', 2); - targetingInstance.setLatestAuctionForAdUnit('code-3', 2); - - // Bid Caching On, No Filter Function - useBidCache = true; - bidCacheFilterFunction = undef; - let bids = targetingInstance.getWinningBids(adUnitCodes); - - expect(bids.length).to.equal(4); - expect(bids[0].adId).to.equal('adid-1'); - expect(bids[1].adId).to.equal('adid-4'); - expect(bids[2].adId).to.equal('adid-5'); - expect(bids[3].adId).to.equal('adid-8'); - - // Bid Caching Off, No Filter Function - useBidCache = false; - bidCacheFilterFunction = undef; - bids = targetingInstance.getWinningBids(adUnitCodes); - - expect(bids.length).to.equal(4); - expect(bids[0].adId).to.equal('adid-2'); - expect(bids[1].adId).to.equal('adid-4'); - expect(bids[2].adId).to.equal('adid-6'); - expect(bids[3].adId).to.equal('adid-8'); - - // Bid Caching On AGAIN, No Filter Function (should be same as first time) - useBidCache = true; - bidCacheFilterFunction = undef; - bids = targetingInstance.getWinningBids(adUnitCodes); - - expect(bids.length).to.equal(4); - expect(bids[0].adId).to.equal('adid-1'); - expect(bids[1].adId).to.equal('adid-4'); - expect(bids[2].adId).to.equal('adid-5'); - expect(bids[3].adId).to.equal('adid-8'); - - // Bid Caching On, with Filter Function to Exclude video - useBidCache = true; - let bcffCalled = 0; - bidCacheFilterFunction = bid => { - bcffCalled++; - return bid.mediaType !== 'video'; - } - bids = targetingInstance.getWinningBids(adUnitCodes); - - expect(bids.length).to.equal(4); - expect(bids[0].adId).to.equal('adid-1'); - expect(bids[1].adId).to.equal('adid-4'); - expect(bids[2].adId).to.equal('adid-6'); - expect(bids[3].adId).to.equal('adid-8'); - // filter function should have been called for each cached bid (4 times) - expect(bcffCalled).to.equal(4); - - // Bid Caching Off, with Filter Function to Exclude video - // - should not use cached bids or call the filter function - useBidCache = false; - bcffCalled = 0; - bidCacheFilterFunction = bid => { - bcffCalled++; - return bid.mediaType !== 'video'; - } - bids = targetingInstance.getWinningBids(adUnitCodes); - - expect(bids.length).to.equal(4); - expect(bids[0].adId).to.equal('adid-2'); - expect(bids[1].adId).to.equal('adid-4'); - expect(bids[2].adId).to.equal('adid-6'); - expect(bids[3].adId).to.equal('adid-8'); - // filter function should not have been called - expect(bcffCalled).to.equal(0); - }); - it('should not use rendered bid to get winning bid', function () { let bidsReceived = [ createBidReceived({bidder: 'appnexus', cpm: 8, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', status: 'rendered'}), diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 6494ead78e7..898b79cdcb5 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -2,6 +2,7 @@ import { getAdServerTargeting } from 'test/fixtures/fixtures.js'; import { expect } from 'chai'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; +import {waitForElementToLoad} from 'src/utils.js'; var assert = require('assert'); @@ -1198,4 +1199,41 @@ describe('Utils', function () { }); }); }); + + describe('waitForElementToLoad', () => { + let element; + let callbacks; + + function callback() { + callbacks++; + } + + function delay(delay = 0) { + return new Promise((resolve) => { + window.setTimeout(resolve, delay); + }) + } + + beforeEach(() => { + callbacks = 0; + element = window.document.createElement('div'); + }); + + it('should respect timeout if set', () => { + waitForElementToLoad(element, 50).then(callback); + return delay(60).then(() => { + expect(callbacks).to.equal(1); + }); + }); + + ['load', 'error'].forEach((event) => { + it(`should complete on '${event} event'`, () => { + waitForElementToLoad(element).then(callback); + element.dispatchEvent(new Event(event)); + return delay().then(() => { + expect(callbacks).to.equal(1); + }) + }); + }); + }); }); diff --git a/test/test_deps.js b/test/test_deps.js new file mode 100644 index 00000000000..e535154b799 --- /dev/null +++ b/test/test_deps.js @@ -0,0 +1,3 @@ +require('test/helpers/prebidGlobal.js'); +require('test/mocks/adloaderStub.js'); +require('test/mocks/xhr.js'); diff --git a/test/test_index.js b/test/test_index.js index 53d75e36176..883f4d0590c 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,6 +1,4 @@ -require('test/helpers/prebidGlobal.js'); -require('test/mocks/adloaderStub.js'); -require('test/mocks/xhr.js'); +require('./test_deps.js'); var testsContext = require.context('.', true, /_spec$/); testsContext.keys().forEach(testsContext); diff --git a/wdio.conf.js b/wdio.conf.js index c9aaa3f160d..4865c03f339 100644 --- a/wdio.conf.js +++ b/wdio.conf.js @@ -9,7 +9,7 @@ function getCapabilities() { return platformMap[os]; } - // only IE 11, Chrome 80 & Firefox 73 run as part of functional tests + // only Chrome 80 & Firefox 73 run as part of functional tests // rest of the browsers are discarded. delete browsers['bs_chrome_79_windows_10']; delete browsers['bs_firefox_72_windows_10'];